`deinit` proposal

Hi folks,

To recap the discussion above, a few comments:

Move init vs decorator

The @destructor style of decorator is found to be confusing when applied to the moveinit case. To confirm Nick’s comment, if moveinit turned into init then per this proposal, it would be written as:

struct Thing:
    var x: Int

    @destructor
    fn __init__(out self, *, move: Self):
        self.x = move.x^

There are ways to address this with different tradeoffs - eg

  1. add the ability for decorators to apply to arguments and have a @destroys decorator or somesuch (not very appealing for complexity reasons),

  2. define the move constructor as a move() method which would take self and yield a new value. Not appealing because it loses “init” syntax and therefore loses the ability to fieldwise initialize self.

  3. teach people that out arguments are not arguments even though they appear in the argument list syntactically (the current default per the proposal).

As many folks have said, these aren’t perfect by any stretch of the imagination. Move constructors are pretty rare to write though - Mojo synthesizes trivial ones, and Rust doesn’t even permit you to write them - so perhaps this is an acceptable weakness of the proposal.

Statement form

I still don’t agree that the statement form adds anything important here. I hear the arguments from proponents for this, but it makes the default and important cases more verbose and more difficult to explain (e.g. the move init and other cases) and - again - Mojo doesn’t depend on the position of this statement, so giving it an imperative form just illuminates confusion about the programming model, it doesn’t dispell it.

ASAP destruction is a thing, and a good thing.

Arg convention form

The major argument against argument convention is that it is confusing because it … isn’t an argument convention. I hear that argument, but against the other tradeoffs of other approaches, it seems like the “least bad” approach at least for now. It is simple, explainable, compact and uniform.

Path forward

The default is to keep what we have: deinit in the argument convention position. If and when we consider resyntaxing the copyinit/moveinit syntax (not a priority as far as I know, the team is focused on landing bigger ticket features) then we can come back to evaluating the tradeoffs and repaint this. Until then, I don’t see an approach that is significantly better-enough to be worth investing in.

-Chris

I agree, I am happy to leave the final syntax for destructors as future work. As long as if/when this conversation re-starts a few months down the line, nobody claims “we already decided that deinit was the right design.” :slightly_smiling_face:

It’s a tricky design problem, and I’d like to see us end up with something clean and simple, even if it takes us a while to figure out.

1 Like

Would it be possible to make deinit „passed on“ in a way so that a function can call another function that takes the value as deinit to destructure it?

This would make deinit feel more consistent and one does not have to change the „argument convention“ from deinit to var just because of an internal refactoring.

Keep deinit:

fn __del__(deinit self):
    _internal_destructure(self)

fn _internal_destructure(deinit self: Self): …

Instead of the current design (switch to var):

fn __del__(var self):
    _internal_destructure(self)

fn _internal_destructure(deinit self: Self): …

Would it be possible to make deinit „passed on“ in a way so that a function can call another function that takes the value as deinit to destructure it?

I’ll have to poke at it, but yes, I believe this should be possible. I’d suggest using _internal_destructure(self^) though :slight_smile:

1 Like

So is there a change coming with a nightly release as this is marked as solution?

Thank you, @clattner !

1 Like

Somehow related because it concerns to argument convention naming: Mojo proposal: renaming `read` to `immut`