Argument exclusivity with arguments from two list elements

Consider the following case:

@value
struct SomeStruct:
    var a: Int

fn copy_to(from_struct: SomeStruct, inout to_struct: SomeStruct):
    to_struct.a = from_struct.a


def main():
    l = List[SomeStruct](SomeStruct(1), SomeStruct(2))
    copy_to(l[0], l[1])

This does not work, since both l[0] and l[1] have the same origin, which is l. However, the underlying memory is not the same. Is there a solution to deal with such issues other than inlining the content of copy_to?

1 Like

Hi @samufi, this is a great question.

I don’t believe there’s a way to make this program compile in today’s Mojo. Per Mojo’s borrowing rules, from_struct and to_struct cannot alias if one of them is mutable.

@clattner, you might be interested in this user’s predicament.

There are various ways that we could extend Mojo to allow your program to be safely expressed. For example, I’m hoping that we will allow arguments parameterized by the same origin to mutably alias:

fn copy_to[o: MutableOrigin](ref[o] from_struct: SomeStruct, ref[o] to_struct: SomeStruct):
    to_struct.a = from_struct.a

In the meantime, you can use UnsafePointers as a workaround. UnsafePointers are allowed to mutably alias:

from memory import UnsafePointer

@value
struct SomeStruct:
    var a: Int

fn copy_to(from_struct: UnsafePointer[SomeStruct], to_struct: UnsafePointer[SomeStruct]):
    to_struct[].a = from_struct[].a


def main():
    l = List[SomeStruct](SomeStruct(1), SomeStruct(2))
    copy_to(UnsafePointer.address_of(l[0]), UnsafePointer.address_of(l[1]))
    print(l[0].a, l[1].a)
1 Like

The simplest workaround is to introduce a copy of the element, so you’re passing a reference to the element instead of the original list, e.g. something like:

l = List[SomeStruct](SomeStruct(1), SomeStruct(2))
var tmp = SomeStruct(l[0])
copy_to(tmp, l[1])

or even:

l = List[SomeStruct](SomeStruct(1), SomeStruct(2))
copy_to(SomeStruct(l[0]), l[1])

It is important that mojo reject the original code. In this case, theoretically it could symbolically analyze that 0 != 1, but it would have to go look at the body of the List.getitem implementation to decide how and if that matters, and we don’t want the type checker to look at full interprocedural information like that.


I wouldn’t recommend or encourage the use of the UnsafePointer thing above btw. Nick is correct that they can mutably alias… but it is undefined behavior when they do. The only reason the code above work is that the list elements don’t alias in this one example, but it would be a dangerously unsafe API to expose to people.

This is why we want UnsafePointer to scream at you by being so visible and long - it is wildly unsafe unless you use it the right way. It is an important tool for building low level data structures, but not something that should generally be exposed for user-level APIs like the above.

1 Like

I am surprised by this. I thought Mojo’s UnsafePointers are compiled the same as C pointers, i.e. they are treated as potentially mutably aliasing one another. (i.e. the compiler doesn’t perform optimizations based on the assumption that unsafe pointers don’t alias.)

Isn’t this why the as_noalias_ptr method exists: to allow programmers to opt into noalias optimizations?

More generally though, I agree that UnsafePointers are not something that 99% of Mojo users should be using, and I probably should have made this much more clear in my original post.

Your suggestion to make a copy of the first argument is much more sensible. (In cases where this isn’t a performance bottleneck.)

Thank you for the insightful responses. I guess the we would really need to see what comes out of Nick’s proposal. For the time being I inlined the entire function elsewhere to prevent the need for a copy or unsafe pointers. Maybe this could be an additional option, though, to allow this kind of stuff on @always_inline functions. (I would understand if this is not done, though, as this might give programmers wrong incentives and break consistency somehow…)

There are definitely some analogies, C pointers are assumed they may alias in most cases (independent of mutability) but in C, int* and float* are assumed to not alias in C (with weird exceptions). C is a mess, and we shouldn’t define Mojo’s behavior in terms of it.

Isn’t this why the as_noalias_ptr method exists: to allow programmers to opt into noalias optimizations?

Yes, but beware that this is a low level hack and is likely to be redesigned or removed, don’t index too much on it :slight_smile:

More generally though, I agree that UnsafePointers are not something that 99% of Mojo users should be using, and I probably should have made this much more clear in my original post.

+1,

-Chris

1 Like

I would like to be 1 of the 1% of Mojo users that uses this to see if something breaks :+1: