Why not an Assign trait?

Hi there,

I was implementing a Bitset structure and implementing Index and IndexMut for it. When I came about writing the body for IndexMut I realised that I must return a &mut to, in this case, a single bit. Because that is not possible, as far as I can tell, I thought that I could maybe return some type of proxy object that, when assigned to, would write to the corresponding bit.

Irrespective of that design, I found out that there are traits for AddAssign, MulAssign, etc., but no Assign to overload the assignment = operator.

Being this perfectly common in C++, I wonder why Rust forbids overloading this particular operator. I found this discussion, but can't find an explanation of the rationale behind this decission.

Any light on the issue?

Thanks,
Fernando

4 Likes

Being sure that assigns are always a move or copy has a lot of nice
properties. You know that it's always inexpensive, for example. It also
means you know it has no side effects.

4 Likes

Perhaps your proxy object can DerefMut to an internal temporary bool, then you can write that back to the original bit once you Drop the proxy.

2 Likes

Related discussion https://www.reddit.com/r/rust/comments/4as7gx/why_make_the_index_trait_so_useless/

2 Likes

I'd want IndexAssign, but not “Assign”.

1 Like

@steveklabnik Ok, predictable behaviour sounds reasonable. Like I said, coming from a C++ background I'll have to chew this a bit, but that's something, thanks!

@cuviper Thanks for the tip! I'll have a look at it.

@malbarbo Those were my next concerns after thinking longer about the Index trait, I'll read it carefully, thanks!

The copy/move constructors in C++ mostly do simple tracking things like bumping refcounts (which you don't need in a language with affine types, everything is a move) and invalidating old containers (because C++ doesn't have dynamic drop). These needs don't map to Rust; having the compiler track drops (and use stack drop flags if necessary), and using affine types is enough. This gets rid of the nullability of most smart pointer types too. This does mean that intrusive data structures are hard in Rust, but those are rare anyway.

If Rust did have a copy constructor / overloadable assignment operator, stuff like vectors couldn't be optimized to memcpys. There's a lot more fiddliness when the relatively simple operation of assignment is able to execute arbitrary code. Rust has a model of "assignments are moves" which is pretty easy to keep track of.

3 Likes

What's the evidence that they are rare (in a system language)?

Anecdotal from my side.

1 Like

Thanks for your explanation! I haven't got that much experience with rust just yet to appreciate the benefits, but as mentioned before, predictable operation seems a reasonable benefit to have. I'll have a look at affine types too, it's a new concept to me.

Why then allow overloading *=, /= and the like? We could as well just leave the multiplication and division operations to overload and assignment as it is. Ergonomics?

Overloading += is an efficiency win if you take an operation like *v += 1 where v is a mutable reference to a sizable vector. It could efficiently go through by value + if v was by value, but I think it needs overloading to handle the &mut self receiver case.

The same way it would be great if IndexAssign existed, so that the same vector could implement v[..] = 0 or similar for setting all (or a range) of its elements.

2 Likes

The popularity of intrusive data structures in practice seems to depend heavily on language ergonomics. In C they are quite common, both ad-hoc (since lol no generics) and macro-based e.g. sys/queue.h) linked lists, chained hash tables, etc. - because both types are more naturally implemented as intrusive. In C++, intrusive data structures are both not part of the STL and relatively annoying to implement (to make it work with the template system you need subclassing), and therefore uncommon.

3 Likes

Because those are paired with the math operators and you already expect the math operators to do nontrivial stuff, so it's okay if the assignment operators do the same.

Overloading = itself means also having the concept of an overloaded copy/move constructor. It's usually unsafe in C++ to have one but not the other, because now you've overloaded one kind of copy/move but the other won't do anything.