Traits and patterns in functions without body?

I am trying to use traits for the first time and got an error pointing me to this article:
https://github.com/rust-lang/rust/issues/35203

After reading that I am still a bit confused. It says to remove & or mut from the parameters in the function definition. What confuses me is why? & or mut seems rather important in a function signature especially when writing traits. If you're going to control how the trait is used, whether it receives a pointer or is mutable seems to be a non-trivial factor.

What am I missing here? I'm still obviously just grasping for the basics, so I'm sure it's a lot and rather important.

It would help to paste in your actual code (perhaps reduced to make it more understandable) and the actual error message. It is not clear to me at least what the problem is from the link you have mentioned.

Here is a simplified piece of the code that should render the error in RLS:

    struct BasicSwitch{
        on:bool,
    }
    impl BasicSwitch{
        fn on(mut self)->Self{
            self.on = true;
        }
        fn off(mut self)->Self{
            self.on=false,
       }
    }
    trait OnOff{
        fn on(mut self)->Self;
        fn off(mut self)->Self;
    }

The error says that patterns aren't allowed in functions without bodies and points to the link I posted previously

Just write

    struct BasicSwitch{
        on:bool,
    }
    impl BasicSwitch{
        fn on(mut self)->Self{
            self.on = true;
        }
        fn off(mut self)->Self{
            self.on=false,
       }
    }
    trait OnOff{
        fn on(self)->Self;
        fn off(self)->Self;
    }

and you’ll be fine :wink: (except that your implementations don’t return anything right now)

The mut in this function signature doesn’t say anything that’s relevant to the caller. When calling the function on or off, you’ll pass an owned value to the function, and the function is then able to mutate its own local variable that holds the argument it has received. It could even – equivalently – do the same thing by moving the argument into a different, mutable, variable, e.g.

        fn on(self)->Self{
            let mut this = self;
            this.on = true;
            todo!() // actually return something
        }

It might also be the case that what you want actually is not a fn(self) -> Self but a fn(&mut self) signature. In this case, do something like

    struct BasicSwitch{
        on:bool,
    }
    impl BasicSwitch{
        fn on(&mut self) {
            self.on = true;
        }
        fn off(&mut self) {
            self.on=false,
       }
    }
    trait OnOff{
        fn on(&mut self);
        fn off(&mut self);
    }

I understand how to make it work, remove mut. The comments on the link say as much, but I don't understand why it's not necessary. Whether it's mutable or not seems non-trivial. I kinda want to understand the reasoning before just rolling forward.

I added some additional stuff to the answer above. Feel free to ask follow-up questions if you have any.

Ok, why is &mut ok and considered necessary, but mut or & on their own (according to the link) not. I'm really not grasping at the root of this. Just trying to understand. Thank you for bearing with me.

Note that &mut self is significantly different from mut self. The meanings are basically:

fn foo(mut self) -> … { /* use `self` */ }
// means about as much as

fn foo(self: Self) -> … { let mut this = self; /* use `this` */ }

whereas

fn foo(&mut self) -> … { /* use `self` */ }
// is short syntax for
fn foo(self: &mut Self) -> … { /* use `self` */ }

i.e. the function expects a mutable reference instead of a owned by-value argument.

The latter (mut self) is a pattern, it’s similar to how

fn f((x, y): (i32, bool)) { /* use x, y */ }

can be used instead of

fn f(pair: (i32, bool)) { let (x, y) = pair; /* use x, y */ }

The former (&mut self) instead changes the type of the argument (which is relevant to the caller); trait declarations allow variable names and type signatures, but generally don't allow more complex patterns.

2 Likes

I swear you're psychic . . lol. You're answering my next question as I type the question . . .

So I guess the &mut is so much different because it's a pointer that it is referencing and it can't necessarily know what's behind it where as mut is handing the entire variable, that it can then handle as necessary.

Does that approximate the difference?

That still doesn't explain why just a plain & isn't necessary . . .

Ah, right... they’re talking about a different thing. There’s a type & and a pattern &. (And also an expression &). E.g.

let x = 42;
//     +-- type
//     v      v-- expression
let y: &i32 = &x;
let &z = y;
//  ^-- pattern

let number: i32 = z;

the pattern &… does dereference a reference. Similar to how

let (a, b) = (1, 2);

means that a is 1 and b is 2, something like

let &a = &1;

means that a is an i32 that’s 1. (This kind of dereferencing generally works best when the value can be copied.)

So a trait does not allow a pattern like

trait Foo {
    fn foo(&x: &i32);
}

where, again, something like

fn foo(&x: &i32) { /* use `x` */ }

would be equivalent to

fn foo(reference: &i32) { let &x = reference; /* use `x` */ }

but something like

trait Foo {
    fn foo(r: &i32);
}

is okay, as is

trait Bar {
    fn bar(&self);
}

because &self is special, like &mut self, and it’s actually a short-hand for self: &mut Self.

By the way, note that even names for arguments in traits are technically superfluous; but they aid in documentation, so they’re allowed. But you can also just do

trait Foo {
    fn foo(_: i32, _: bool);
}
impl Foo for Bar {
    fn foo(number: i32, condition: bool) { … }
}

leaving out the names by using the _ pattern; and similarly, when implementing a trait, you could also be using a different name as the one in the trait declaration; or using a pattern when the trait declaration didn’t (and couldn’t).

Only for self-arguments the name is important, only the first argument may be named self and if it is, then method-call syntax is supported.

It's interesting to note that anonymous parameters were deprecated, so it's probably a bit stronger than "allowed". This occasionally throws me since names are optional in fn() types and forbidden in Fn() traits.

1 Like

Wow, I had no idea that was a thing. I wonder if it's a mistake.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.