Shouldn't `&self` be `ref self` to be consistent?

Wouldn't &self better be written ref self to be consistent with patterns elsewhere?

E.g.

impl Foo {
    pub fn f(ref mut self) { ... }
}

so as to be consistent with:

let ref mut foo = an_object;
3 Likes

&T is consistent with &val inside patterns. &val matches a reference and puts the pointed to value in val.

But that's exactly the opposite of what happens for &self. self becomes a reference.

1 Like

Exactly! A pattern and value are meant to be the exact opposite. So the expression Some(1) makes an Option<u32>, while the pattern Some(val) matches some Option<u32>.

1 Like

Function parameters are patterns.

&self is a symtax sugar for self: &Self and nothing more. Shorter form was introduced as we write it every day.

7 Likes

I understand, all I'm saying is ref self would be more consistent sugar.

1 Like

I'd say ref self is equally inconsistent with &self since it neither reduce nor adds up any reference.

6 Likes

The ref self means we have the current object by value but are binding self to a reference to it, which is different to what you think it will do.

Let's use this example:

struct Foo;

fn type_name<T>(_: T) -> &'static str {
    std::any::type_name::<T>()
}

impl Foo {
    fn ref_method(ref this: Self) {
        println!("ref method: {:?}", type_name(this));
    }

    fn ref_borrow_method(ref this: &Self) {
        println!("ref borrow method: {:?}", type_name(this));
    }

    fn ampersand_self(&self) {
        println!("ampersand self: {:?}", type_name(self));
    }
}

fn main() {
    let f = Foo;
    f.ampersand_self();
    Foo::ref_borrow_method(&f);
    Foo::ref_method(f);
    // Foo::ref_method(f);  // ERROR: Use of moved value
}

(playground)

The generated output is this:

ampersand self: "&playground::Foo"
ref borrow method: "&&playground::Foo"
ref method: "&playground::Foo"

If we had ref self (the ref_borrow_method() method) as syntactic sugar for a &Self parameter then we'd actually end up with the self variable being bound to a &&Self, adding one more level of indirection. Whereas if we had ref self: Self, the self variable would be bound to a &Self, but we would consume the Self object by value.

So it might feel more consistent, but using the ref keyword will add another level of indirection.

6 Likes

I think this "should" is correct and have often wished that Rust used &Self instead of &self (etc.) for the same reason. Of course, it's way too late to change something so fundamental about the language.

5 Likes

It's a bit unfair to come up with the example of ref this: &Self, because here you combine two things (& on the type and ref on the variable name). That's not what the original post was about.

@Michael-F-Bryan, what you showed is that the ref_method indeed returns "&playground:Foo", in the same way as the ampersand_self method does.

Considering that the syntactic sugar places the & before a variable name – and not before a type – I would agree with @tczajka's original post.

However, as ref this: &Self actually consumes the value, the ref syntax could be be dangerously misleading. Afterall &self is a special sort of syntax anyway, as @Hyeonu pointed out:

Nonetheless, I think @tczajka has a point here. I still prefer &self though (and dislike ref anyway).

1 Like

It should never be too late :stuck_out_tongue: (but I prefer &self anyway).

It's too late to implement without breaking Rust's guarantee of backward compatibility. Thus Rust avoids becoming a negative lesson in edition churn. (I'm looking at you, Python.)

6 Likes

Rust (so far) made an amazing job to correct errors from the past while maintaining backward compatibility (also because of the Editions system).


Either way: I like when people question language constructs (I often do that myself :sweat_smile:), and this forum should be a place to get (constructive) feedback on breaking ideas.

1 Like

This forum (URLO) is an okay place to initiate such exploratory discussions. The parallel Rust forum IRLO is for serious consideration of language and tooling (rustc, cargo, etc) changes; many of the language and tooling designers read that forum but seldom visit URLO.

I think the original post didn't mean to propose a language change, but was merely an interesting thought-experiment. For me, as a user, it showed up an interesting perspective.

There is no danger of seeing ref self in the next version of Rust, so I guess we shouldn't criticize the original idea/thought that badly. I find the idea interesting, so thumbs up from my side :+1:

That doesn't mean I want to change the language. I like &self.

1 Like

Me too! Given how often we need self: &Self it's a lot more convenient to be able to simply write &self.

I'm a bit surprised that this moves the parameter into the function (consumes it):

fn foo(ref x: Foo) {}

Wouldn't it make sense to treat the signature of this function as equivalent to the following, and not move the object into the function?

fn foo(x: &Foo)

After all, this works and doesn't consume:

let a = Foo;
let ref b = a;
let ref c = a;
2 Likes

Now that you say it, it surprises me too. :thinking:

But indeed it does (playground).

1 Like

The technical explanation is that passed parameters are value expressions but the initializer is a place expression.

Binding in place for function calls would be backwards-incompatible.

fn foo(ref mut x: i32) {
    *x = 0;
}

fn main() {
    // n.b. `i32` implements `Copy`
    let i = 42;
    foo(i);
    assert_eq!(i, 42);

    let mut j = 42;
    let ref mut x = j;
    *x = 0;
    assert_eq!(j, 0);
}
2 Likes