How to return a mutable reference to a dict entry?

I want to write a function returning a reference to a Dict element. Previously, this could be done by dereferencing the result of Dict.get_ptr(key). Now, as my_dict[key] already returns a reference I thought this should be even more straightforward, but I encounter issues with the origins. Can someone point out my error?

This is my attempt (Mojo 25.4.0.dev2025052405):

@fieldwise_init
struct Element(Copyable, Movable):
    var value: Int


@fieldwise_init
struct Container:
    var data: Dict[Int, Element]

    fn get(ref self, key: Int) raises -> ref [__origin_of(self.data.__getitem__(key))] Element:
        return self.data.__getitem__(key)


def main():
    s = Container({1: Element(1)})
    s.get(1) = Element(2)
    print(s.data[1].value)

This complains about the result of get not being mutable, and this remains the case even if I define it as get(mut self, key: Int):

/path/to/module.mojo:16:10: error: expression must be mutable in assignment
    s.get(1) = Element(2)
    ~~~~~^~~

Other attempts

I have also tried the following:

  • Dereferencing a pointer to the dict element (same error):
     fn get(mut self, key: Int) raises -> ref [__origin_of(self.data.__getitem__(key))] Element:
         return Pointer(to=self.data.__getitem__(key))[]
    
  • Using the straightforward implementation
     fn get(mut self, key: Int) raises -> ref [self.data[key]] Element:
         return self.data[key]
    
    which gives me another error:
    /path/to/module.mojo:10:56: error: value of type 'Element' doesn't have a memory origin
     fn get(mut self, key: Int) raises -> ref [self.data[key]] Element:
                                               ~~~~~~~~~^~~~~
    /path/to/module.mojo:10:56: error: cannot infer origin for a function result
     fn get(mut self, key: Int) raises -> ref [self.data[key]] Element:
                                               ~~~~~~~~~^~~~~
    
  • Trying to fix the origin error above
     fn get(mut self, key: Int) raises -> ref [__origin_of(self.data.__getitem__(key))] Element:
         return self.data[key]
    
    receiving
    /path/to/module.mojo:16:10: error: expression must be mutable in assignment
     s.get(1) = Element(2)
     ~~~~~^~~
    /path/to/module.mojo:11:25: error: cannot bind a non-memory value to a 'ref' argument in return value
         return self.data[key]
    

Why don’t you just make a setter in your Container struct?

  1. Because a reference return allows to create a corresponding pointer
  2. Because I need to implement less code then
  3. Because I think it should be doable to return a mutable reference, and if not I want to understand why.

So, it is not only that I want to do a set operation as in the example, but I want to know how to do make it work with a reference.

1 Like

@samufi ,

It seems that Dict.__getitem__() always returns an immutable ref.
Doing the same with a List works fine because it returns a mutable ref:

@fieldwise_init
struct Element(Copyable, Movable):
    var value: Int


@fieldwise_init
struct Container:
    var data: List[Element]

    fn get(ref self, key: Int) raises -> ref [self.data.__getitem__(key)] Element:
        return self.data.__getitem__(key)


def main():
    s = Container([Element(1)])
    print(s.data[0].value)
    s.get(0) = Element(2)
    print(s.data[0].value)

You might need to use a Pointer for having a mutable ref from a Dict:

from memory import ArcPointer

@fieldwise_init
struct Element(Copyable, Movable):
    var value: Int


@fieldwise_init
struct Container:
    var data: Dict[Int, ArcPointer[Element]]

    fn get(ref self, key: Int) raises -> ref [self.data.__getitem__(key)] ArcPointer[Element]:
        return self.data.__getitem__(key)


def main():
    s = Container({1: ArcPointer(Element(1))})
    print(s.data[1][].value)
    s.get(1)[] = Element(2)
    print(s.data[1][].value)

BTW: If you would like the Container to behave like a collection type you can replace get() with __getitem__(). You can use a Pointer instead of an ArcPointer too:

from memory import Pointer

@fieldwise_init
struct Element(Copyable, Movable):
    var value: Int

alias ElementPointer = Pointer[Element, MutableAnyOrigin]

@fieldwise_init
struct Container:
    var data: Dict[Int, ElementPointer]

    fn __getitem__(ref self, key: Int) raises -> ref [self.data.__getitem__(key)] ElementPointer:
        return self.data.__getitem__(key)


def main():
    a = Element(1)
    s = Container({1: ElementPointer(to=a)})
    print(s.data[1][].value)
    s[1][] = Element(2)
    print(s.data[1][].value)

Thank you. Yes, I see now that the self in Dict.__getitem__ does not have the ref keyword in front of itself and is thus immutable. I am just not sure whether this is intended behaviour.

The solution with ArcPointer puts all dict elements at random places in memory, so this is not a satisfactory solution though it surely works.

If there is anyone here who can say whether the missing ref keyword is intentional, I would be grateful for a hint as to why it is not there.

Hi @samufi

You are right. I made a copy of dict.mojo and added the ref self to __getitem__() and it just works fine. A mutable ref is returned and the value can be mutated.

my_dict.mojo:

...
fn __getitem__(
        ref self, key: K
    ) raises -> ref [self._entries[0].value().value] Self.V:
...

container.mojo:

from my_dict import Dict

@fieldwise_init
struct Element(Copyable, Movable):
    var value: Int


@fieldwise_init
struct Container:
    var data: Dict[Int, Element]

    fn __getitem__(ref self, key: Int) raises -> ref [self.data.__getitem__(key)] Element:
        return self.data.__getitem__(key)


def main():
    s = Container({1: Element(1)})
    print(s.data[1].value)
    s[1] = Element(2)
    print(s.data[1].value)

How to proceed with this issue?

I am not sure how to proceed. Maybe filing an issue or to make a PR directly… Or someone who knows sees this.

I filed an issue here: [BUG] Dict.__getitem__ always returns immutable references · Issue #4695 · modular/modular · GitHub

1 Like

There’s definitely some funny stuff happening here. In the meantime, you can work around this by using the Dict._find_ref method instead of getitem.

2 Likes