Proposal: Allow manually initializing all fields without init

‘two_fields’ used with all fields manually initialized but without calling an ‘init’ method

As proven by __mlir_op.`lit.ownership.mark_initialized`( __get_mvalue_as_litref(two_fields)) this is an completely artificial restriction. The function make_two_fields generates correct machine code.

struct TwoFields(Movable):
    var first_field: Int
    var second_field: Int


def make_two_fields() ->  TwoFields :
    var two_fields: TwoFields
    two_fields.first_field = 100
    two_fields.second_field = 200
    __mlir_op.`lit.ownership.mark_initialized`(
        __get_mvalue_as_litref(two_fields)
    )
    return two_fields^

The verbose alternative with initializers would be:

@fieldwise_init
struct TwoFields(Movable):
    var first_field: Int
    var second_field: Int



def make_two_fields() ->  TwoFields :
    var first_field = 100
    var second_field = 200
    var two_fields = TwoFields(first_field=first_field, second_field=second_field)
    return two_fields^

Hi qja,

Can you share more of the motivation you have for this? Just because something “could” be done doesn’t mean it “should” be done.

You’re right that Mojo has unsafe features, including direct access to MLIR operators like this, however we want safe code to be the default.

“make_two_fields” seriously violates the encapsulation of TwoFields.

See Deep dive - Instance initialization | Mojo - there is a difference between field initialisation and logical initialisation. While your example initialises fields, it doesn’t logically initialise the TwoFields instance, because it does not run through any invariants that may be enforced by the type.

Speaking as one of the resident “I have a horrible use-case for this weird and highly unsafe feature” people in the Mojo community, I don’t think this is a good idea. We do have the capability, but it’s deeply unsafe. For instance, currently the compiler reserves the ability to do things like make use of unused bits in a type to store “niche” values, and it also defines the behavior for what padding bytes need to be initialized to. If TwoFields was 12 bytes instead of 16 (on a 64-bit system), then the compiler would likely pad it for alignment reasons. That leaves 4 bytes that it can use as it pleases. Those padding bytes would be initialized as part of the constructor, and this bypasses that.

From a theoretical perspective, it is also a gross violation of encapsulation. While there are good reasons to sometimes break encapsulation, I consider it fine to throw some syntactic salt on there so that it’s easy to find anywhere someone does this, because places like this are likely sites for bugs to occur so being able to grep for them is very important.

If you want shorter initialization syntax, your example can be rewritten as:

@fieldwise_init
struct TwoFields(Movable):
    var first_field: Int
    var second_field: Int

def make_two_fields(out two_fields: TwoFields)::
    var first_field = 100
    var second_field = 200
    two_fields = {first_field, second_field}

I’d also like to know more about where you ran into this, since most of the places I can think of that this would be useful would actually be better served by “friend” constructors that are only accessible in some scope (for instance to implement the factory pattern).