#Adding __takeinit__ makes a difference in assignment

21 messages · Page 1 of 1 (latest)

tepid crown
#

I'm playing with HeapArray and I cannot understand what is going on.

struct HeapArray:
    var data: Pointer[Int]
    var size: Int

    fn __init__(inout self, size: Int, val: Int):
        self.data = Pointer[Int].alloc(size)
        self.size = size

        for i in range(size):
            self.data.store(i, val)
    
    fn dump(self):
        for i in range(self.size):
            print(self.data.load(i))

    fn __copyinit__(inout self, existing: Self):
        print("HeapArray copyinit")
        self.data = Pointer[Int].alloc(existing.size)
        self.size = existing.size
        for i in range(self.size):
            self.data.store(i, existing.data.load(i))

    fn __moveinit__(inout self, owned existing: Self):
        print("HeapArray moveinit")
        print("existing address: ", existing.data.__as_index())

        self.data = existing.data
        self.size = existing.size
        existing.data = Pointer[Int].get_null()
        existing.size = 0

    fn __takeinit__(inout self, inout existing: Self):
        print("HeapArray takeinit")
        self.data = existing.data
        self.size = existing.size
        existing.data = Pointer[Int].get_null()
        existing.size = 0

    fn __del__(owned self):
        print("Memory freed at address: ", self.data.__as_index())

fn main():
    let a = HeapArray(3, 2)
    print("a address: ", a.data.__as_index())
    let b = a 
    print("b address: ", b.data.__as_index())
    a.dump()
    b.dump()
a address:  94060226983856
HeapArray copyinit
HeapArray moveinit
existing address:  94060215912848
b address:  94060215912848
2
2
2
Memory freed at address:  94060226983856
2
2
2
Memory freed at address:  94060215912848

This is super weird to me. It calls copyinit and moveinit, why?

hollow sparrowBOT
#

Congrats @tepid crown, you just advanced to level 5!

snow bison
#

Hey Nick, couple things here: first, you can drop takeinit from the picture. It's a weird thing that hasn't worked out well - so we're moving it to be a formal ".take()" method with its own trait in the future, instead of being a magic dunder method that is mixed into ^ semantics.

#

It's not obvious to me why the move is being called. It should just call the copyinit. I'll investigate.

#

Hrm, I'm not really sure. Top of tree on my laptop seems to work:

a address:  105553166371936
HeapArray copyinit
b address:  105553166499840
2
2
2
Memory freed at address:  105553166371936
2
2
2
Memory freed at address:  105553166499840
#

It is entirely possible that there was a transient bug that got introduced and fixed. The entire internals of the compiler are getting replumbed to use references instead of raw pointers, and this work is now settling out. It is possible that an extraneous copy was getting made because of a conversion from ptr<->ref or something.

#

Oh, I didn't notice that this was correlated to adding takeinit actually! Well that make sense why it is fixed 🙂

#

Sneak peak from the next release's changelog:

- The `__takeinit__` special constructor form has been removed from the
  language.  This "non-destructive move" operation was previously wired into the
  `x^` transfer operator, but had unpredictable behavior that wasn't consistent.
  Now that Mojo has traits, it is better to model this as an explicit `.take()`
  operation on a type, which makes it clear when a lifetime is ended vs when the
  contents of an LValue are explicitly taken.
tepid crown
#

Hi Chris, thank you very much for your insightful response and for taking the time to investigate my question. Your explanation has been incredibly helpful, and I now have a clearer understanding of the upcoming changes regarding takeinit.

I am excited to hear about the rapid evolution of Mojo and the updates regarding takeinit. In my view, takeinit seemed like a less memory-safe version of moveinit, so I am pleased with the shift towards an explicit .take() method.

snow bison
#

Cool, it really helps clarify the model as well. It was very weird how x^ would sometimes end a lifetime and sometimes not.

rough harness
hollow sparrowBOT
#

Congrats @rough harness, you just advanced to level 7!

snow bison
#

x^ is still allowed and is used when you want to end the lifetime of a value. This is an exotic thing that system programmers may care about (e.g. with move only or non-movable types) not something that app-level programmers will care about.

#

this does refine/clarify/narrow that x^ does though!

rough harness
#

I see, but still, it will be much clearer if keyword / method will be used instead of single char which does not express its intention without diving into docs

#

Mojo may be way, way more accessible for low level programming, because it does not hides this complexity behind magical chars. Its syntax is self explanatory. Only "^" differs here. I see Mojo as a bridge which connects high and low level programming in very natural way. Every inconsistency against, looks strange imho

neat osprey
#

I would argue that exotic things should look exotic. I think we should distinguish confusion cause by unfamiliarity (what does let mean) from those caused by ambiguity (std::move usually comes in quantified form).
I'm against using the name take for non-destructive moves as well. It looks too generic, and takes a perfectly good method name away from application developers. Imagine using a torch like api, where take sometimes means array indexing and sometimes move. Why don't we just call it nondestructive_move, take_lvalue, or something as ugly and long (again, exotic things should look exotic)? Moreover, it's always easy to migrate from a long and ugly name to a shorter one (if people really think a short generic name is a good one).

rough harness
neat osprey
quasi spear
rough harness