State of postfix borrow/deref

Hello, I often see people want syntax like ptr.*.field or value.&.method(). Even zig has something like that.

What is current state of such syntax? Is there an open RFC for this? Or is someone working on such an RFC? Is there some consensus around that feature?

Thank you

3 Likes

I am aware of nothing beyond the idea in general.

3 Likes

Postfix is absolutely better. One big issue is that * and & are already binary operators, but .&. and .*. are pretty awful.

I think my preferred option would be to just use a word instead:

&x
// becomes
x.ref
x.borrow
x.bor

*x
// becomes
x.deref
x.der
1 Like

Assuming the idea is to add a feature and not change the current features as they exist: I don't really see a good reason to turn Rust into C++, by which I mean an overly complex language that has a multitude of ways to even do the simplest things. All that ends up doing in the long run is fracturing the ecosystem, and ensuring nobody really understands the language.

Borrowing and references already exist, and they work, today.

Additionally, I see no interesting semantics being added here. It's just syntactic sugar, and where sugar is piled on, diabetes will follow.

3 Likes

From Josh on Zulip:

At the lang summit at RustConf this year, we talked about having .& and .* or similar. Syntax bikeshedding needed, but general support for doing it.

https://rust-lang.zulipchat.com/#narrow/channel/318377-t-lang.2Froadmap-2024/topic/.60.2Eas_ref.28.29.60.20operator.2C.20reserving.20a.20symbol.3F/near/477598180

9 Likes

In my ideal world, every unary operator would be postfix. The try operator ? is already postfix, as is .await. We could have postfix not ! (but that would conflict with macros), deref, and borrow as well.

5 Likes

Making negation operators (unary -, !, ~) be postfix might be too much of a divergence from standard mathematical notation.

I also would like dereference operators to be postfix, though.

1 Like

Maybe let's bikeshed syntax/scooe here? Or is it worth a separate topic?

Pretty many languages, accidentally including Rust, already have ?. syntax. Perhaps *. / &. would be more consistent than .*. or .&.:

let n = line?.len();
let x = ptr*.field;
let z = value&.method();

I'm not sure, though, if it is much better than simple and already available methods:

let x = ptr.deref().field;
let z = value.as_ref().method();
2 Likes

The color of the shed I'm most excited about is postfix (or is it infix?) negation:

if very.long.expression.vec.!empty() {

3 Likes

Rust 2024 is getting more match ergonomics, making ref unnecessary in even more cases. I assume that eventually the ref keyword will be very obscure and unknown to most Rust users. So even though .ref is nice to type, it may look weird and not be obviously related to borrowing. Also, if patterns are duals of expressions, then ref in expressions could be interpreted as a dereference.

1 Like

I like this. ptr*.field is how pointer deref should have been spelled all the way from the beginning of C. I had to think for a moment about what value&.method means, but having done that thinking once, it's clear and natural.

Despite what I said earlier re divergence from math notation, I am not enthusiastic about this because I feel like it's too easy for the ! to get lost in the middle of the chain. Actually I think it's too easy for the ! to get lost at the beginning of the chain as well. This may actually be a problem with the sigil rather than its position. ! is tall and thin and means something completely different if it appears at the end of an identifier. It's easier to read past it without noticing it, than it is for - or ~.

If not were a keyword, I would be perfectly happy with

if not very.long.expression.vec.empty() {

not (and and and or) operator keywords are obviously not happening, but how about .not() as a method on bool?

1 Like

That actually exists, though you'll need to import the trait explicitly. I guess we could add an inherent method that just works on bool? I believe this has been discussed before.

2 Likes

Definitely a huge :+1: for postfix deref. :slight_smile: I don't recallt ever wanting postfix borrow, though.

I've wanted postfix ! a couple of times, and sometimes wanted it badly enough to write it as .not() after importing std::ops::Not...

Ah yes having this as an inherent method would be nice, then it doesn't have to be imported.

This one I really don't like. Having the ! right in the middle makes it way too easy to miss, and it's also conceptually not clear which pattern this follows. We don't have any other case of an operator that you put between the . and the following function/field but that applies after the function/field.

3 Likes

Hmm, I can't either. It would only ever come up in a situation where the next thing in the expression isn't the dot operator, since dot implicitly borrows as necessary.

I'd support that just for the sake of better documentation. .not() showing up next to .then() and .then_some() in the sidebar of https://doc.rust-lang.org/std/primitive.bool.html would be much more discoverable than requiring people to dig through the long list of trait impls for bool to find the Not trait. Which it will only occur to them to do if they've made the mental leap to realize that operator trait methods can be called like regular methods if you import the trait.

1 Like

If we dream here, why not introduce prefix not?

e.g.

if very.long.expression.vec.not.empty() {

It's not far away from .! and it's way more readable at the same time.

1 Like

Isn't this kind of useless? Accessing a method/field already borrows/derefs automatically, so you can just do ptr.field and value.method().

I'm a big fan of this one since it extends well to ptr**.field without a bunch of extra noise.

Perhaps only in greenfield, though, since obviously p***q isn't going to go well in Rust.

Sadly it has parser issues with tuple fields and floats: ptr * .4 is already valid code.

3 Likes

What the use case is?

let a = foo.bar; // move/copy
let b = &foo.bar; // borrow foo.bar
let c = &mut foo.baz; // borrow foo.baz mutable.
value.method(); // if we already has `fn method(&self)` for value, using value.method() perhaps is the same to value&.method()

I cannot really think about some real cases about the *. and &. grammar.