M
Modular•2w ago
Deftioon

Is there a way to reflect changes to a variable in the reference?

Here's a small reproducible example:
struct MyList:
var list: List[Int]

fn __init__(out self):
self.list = List[Int]()

fn __getitem__(self, index: Int) -> ref[self.list] Int:
return self.list[index]

fn append(mut self, value: Int) -> None:
self.list.append(value)

fn do_stuff(mut self, index: Int) -> None:
self.list[index] = 1

fn main() raises -> None:
var mylist = MyList()
mylist.append(0)
var q1 = mylist[0]
print(q1)
mylist.do_stuff(0)
print(q1)
print(mylist[0])
struct MyList:
var list: List[Int]

fn __init__(out self):
self.list = List[Int]()

fn __getitem__(self, index: Int) -> ref[self.list] Int:
return self.list[index]

fn append(mut self, value: Int) -> None:
self.list.append(value)

fn do_stuff(mut self, index: Int) -> None:
self.list[index] = 1

fn main() raises -> None:
var mylist = MyList()
mylist.append(0)
var q1 = mylist[0]
print(q1)
mylist.do_stuff(0)
print(q1)
print(mylist[0])
This outputs:
0
0
1
0
0
1
When I want it to output
0
1
1
0
1
1
My logic was that because q1 is a reference to mylist.list , whenever I change mylist.list, it should also be reflected in q1. But it seems q1 is copying the value instead of taking a reference? Am I misunderstanding how references work as a whole?
26 Replies
Darkmatter
Darkmatter•2w ago
I think you've managed to find a language bug. Unless I'm missing something, this shouldn't compile at all. The "spooky action at a distance" definitely isn't allowed, but you are mutating the list while holding an immutable reference to it, so it should error.
Deftioon
DeftioonOP•2w ago
😨
Darkmatter
Darkmatter•2w ago
Welcome to pre-1.0 languages. Borrow checkers are compilicated and we will see bugs for a while.
Deftioon
DeftioonOP•2w ago
I see, should I file an issue?
Darkmatter
Darkmatter•2w ago
Please file a bug.
Deftioon
DeftioonOP•2w ago
👌
Darkmatter
Darkmatter•2w ago
Note that q1 should be an immutable reference, and that mylist.do_stuff(0) manipulates the same origin.
Deftioon
DeftioonOP•2w ago
Mhm mhm
ModularBot
ModularBot•2w ago
Congrats @Deftioon, you just advanced to level 3!
Deftioon
DeftioonOP•2w ago
also, i tried
fn main() raises -> None:
var mylist = MyList()
mylist.append(0)
var q1 = mylist[0]
print(q1)
q1 = 1
print(q1)
print(mylist[0])
fn main() raises -> None:
var mylist = MyList()
mylist.append(0)
var q1 = mylist[0]
print(q1)
q1 = 1
print(q1)
print(mylist[0])
should q1 = 1 be allowed or is it reassigning the variable
Darkmatter
Darkmatter•2w ago
q1 = 1 should be denied because that should be an immutable reference.
Deftioon
DeftioonOP•2w ago
Alright, thanks for the help. Would you happen to know how I could achieve the desired behaviour?
Darkmatter
Darkmatter•2w ago
The desired behavior violates mutable xor alias, so UnsafePointer if you really, really need to do it.
Deftioon
DeftioonOP•2w ago
Ah, okay. Thanks
Darkmatter
Darkmatter•2w ago
This is the trade-off of the borrow checker. It stops you from doing things like this, but it also stops fun bugs like invalidated references due to an append.
Deftioon
DeftioonOP•2w ago
:D makes a lot of sense
samufi
samufi•2w ago
I thought this is intended behaviour for register passable types. Since they are always passed by copy and not by reference, q1 is not a reference to anything but a simple copy. If it were a reference, you'd need to dereference it first, and the issue Owen pointed out would occur. I may be wrong w.r.t. trivial types; after trying it out I think this also happens at other types. But I am sure that q1 = mylist[0] creates a copy.
@value
struct MyInt:
var value: Int
fn __copyinit__(out self, other: Self):
print("Copy")
self.value = other.value



struct MyList:
var list: List[MyInt]

fn __init__(out self):
self.list = List[MyInt]()

fn __getitem__(self, index: Int) -> ref[self.list] MyInt:
return self.list[index]

fn append(mut self, value: MyInt) -> None:
self.list.append(value)

fn do_stuff(mut self, index: Int) -> None:
self.list[index].value = 1

fn main() raises -> None:
var mylist = MyList()
mylist.append(MyInt(0))
print("Will this copy?")
var q1 = mylist[0]
print(q1.value)
mylist.do_stuff(0)
print(q1.value)
print(mylist[0].value)
@value
struct MyInt:
var value: Int
fn __copyinit__(out self, other: Self):
print("Copy")
self.value = other.value



struct MyList:
var list: List[MyInt]

fn __init__(out self):
self.list = List[MyInt]()

fn __getitem__(self, index: Int) -> ref[self.list] MyInt:
return self.list[index]

fn append(mut self, value: MyInt) -> None:
self.list.append(value)

fn do_stuff(mut self, index: Int) -> None:
self.list[index].value = 1

fn main() raises -> None:
var mylist = MyList()
mylist.append(MyInt(0))
print("Will this copy?")
var q1 = mylist[0]
print(q1.value)
mylist.do_stuff(0)
print(q1.value)
print(mylist[0].value)
Darkmatter
Darkmatter•2w ago
I'd need to go dig up some old discord threads, but I think you are still able to have a reference to a register passable type and the compiler needs to make it look like you're using a pointer even if you aren't.
samufi
samufi•2w ago
This may be true (and would be useful in some instances). But I think that after the old references were renamed to Pointer, references behave in a way that they cannot be stored in a variable.
Darkmatter
Darkmatter•2w ago
You can still have a reference stored in a variable, otherwise a lot of stuff gets very messy.
samufi
samufi•2w ago
Can you provide an example?
Darkmatter
Darkmatter•2w ago
Almost anything where you want to partially borrow a struct member.
samufi
samufi•2w ago
Is there any instance where you can create a ref[...] ... explicitly or where a = f() with f -> ref[...] ... does something else than a copy?
Darkmatter
Darkmatter•2w ago
It shouldn't try to copy until you pass a ref to something which needs owned. Try making a non-copyable, non-movable type, then putting it in a struct (foo), then doing var bazz = foo.bar.
samufi
samufi•2w ago
struct Bar:
fn __init__(out self):
pass

struct Foo:
var bar: Bar
fn __init__(out self):
self.bar = Bar()

fn main() raises -> None:
foo = Foo()
bazz = foo.bar
struct Bar:
fn __init__(out self):
pass

struct Foo:
var bar: Bar
fn __init__(out self):
self.bar = Bar()

fn main() raises -> None:
foo = Foo()
bazz = foo.bar
gives you the error
/mojo_proj/source/prog.mojo:13:13: error: 'Bar' is not copyable because it has no '__copyinit__'
bazz = foo.bar
~~~^~~~
mojo: error: failed to parse the provided Mojo source module
/mojo_proj/source/prog.mojo:13:13: error: 'Bar' is not copyable because it has no '__copyinit__'
bazz = foo.bar
~~~^~~~
mojo: error: failed to parse the provided Mojo source module
Same error with
struct Bar:
fn __init__(out self):
pass


struct Foo:
var bar: Bar
fn __init__(out self):
self.bar = Bar()

fn get(self) -> ref[self.bar] Bar:
return self.bar


fn main() raises -> None:
foo = Foo()
bazz = foo.get()
struct Bar:
fn __init__(out self):
pass


struct Foo:
var bar: Bar
fn __init__(out self):
self.bar = Bar()

fn get(self) -> ref[self.bar] Bar:
return self.bar


fn main() raises -> None:
foo = Foo()
bazz = foo.get()
Darkmatter
Darkmatter•2w ago
Huh, I could have sworn that worked.

Did you find this page helpful?