*& meaning

Whilst looking at the Move stdlib, some *& operator usages cropped up. I cannot seem to find where this would be documented; it came up in the stdlib in modules/libra_count.mvir, and when writing some code, it seemed to be necessary in order to get it to work. I’ve considered a couple ideas for what it means. I’m guessing that & and * on their own reference and dereference as would be expected in Rust/C, and as seems to work, but this might not be correct.

  1. It can occasionally crop up in Rust, for dereference conversions. Is something similar happening here?
  2. It seems to be used with field accesses and move statements, as in *&move(rsc).field. Is this some special syntax involved with either accessing fields (as in & x . y is a single operation accessesing a reference’s field) or with moving references where &move(...) is the operation?

Removing the *& doesn’t seem to work, at least in the programmes, so it definitely seems to be doing something, but as to what, there is uncertainty.

Thanks for any help!

Thanks for your question! And its definitely a bit of an oddity that pops up in Move IR programs

I’m guessing that & and * on their own reference and dereference as would be expected in Rust/C

This is exactly correct. As such, *& is borrow followed by a dereference (BorrowField and ReadRef bytecode instructions).

In the Move bytecode, the only way to interact with the fields of a given struct is by reference. So in order to get a copy of a value in a struct, a programmer must first borrow a reference to the field (with &/BorrowField), and then a copy of the value bound to that field can be created by using deref (with */ReadRef).

The Move IR is intended to be a thin, syntactic wrapper over the bytecode. As such, the mechanism for reading values out of a field is made explicit with *& a borrow followed by a dereference.
In the grammar for the IR, dereference works over any expression. Similarly borrowing a field works over any expression. So you could borrow the field first, and then dereference it later. For example:

field_ref = &move(rsc).field;
field_value = *move(field_ref);

In this way, *& is not a special operator, but the composition of two distinct operations.

  1. It can occasionally crop up in Rust, for dereference conversions. Is something similar happening here?

If you are familiar with Rust, think of *&move(rsc).field as being roughly equivalent to src.field.clone(). There is no implicit copying out of field references like there is in Rust (for values that implement the Copy trait). So in the Move IR, a programmer must use the equivalent of clone(), i.e. *&, anywhere that a copy of a field value is needed.

  1. It seems to be used with field accesses and move statements, as in *&move(rsc).field . Is this some special syntax involved with either accessing fields (as in & x . y is a single operation accessing a reference’s field) or with moving references where &move(...) is the operation?

This might be more detail than you need, but let me unpack *&move(rsc).field.

  • This is a composition of 3 separate instructions: A move of a local, a borrowing of a field, and a reading of a reference
  • move(rsc)
    • Each time a local is accessed, the value of that local can either be copied or moved. This must be specified in IR with copy(rsc) or move(rsc) which correspond to the bytecode instructions of CopyLoc and MoveLoc respectively
    • If copy(rsc) is used, a copy of the value stored in that local is put on the stack
    • If move(rsc) is used, the value is moved out of the local and onto the stack. The local rsc cannot be accessed again, until it is assigned a new value
  • &move(rsc).field
    • This borrows a field. As stated above, the only way to access fields of a struct is by reference
    • The reference corresponding to move(rsc) is “extended” to now be a reference accessing field
    • Concretely, the reference move(rsc) is popped off the stack and the reference for field is pushed on the stack
  • *&move(rsc).field
    • This is a dereference. It “reads” the value from the reference
    • When dereferencing, the reference is not modified, and a copy of the value in the reference is created
    • Concretely, the reference &move(rsc).field is popped off the stack, and a copy of the value at that location is pushed on the stack
  • The bytecode instructions for this expression would be:
MoveLoc *rsc*
BorrowField *field*
ReadRef

(Note rsc and field would be represented as indexes in the bytecode)

2 Likes