1

I have a struct called Layer and a save_as_ppm function inside a impl block, the signature of the function is this:

fn save_as_ppm(&self, filename: &str){}

But other functions inside the impl block have &mut self parameter so when I create an instance I have to make it mutable (I don't know if it's called an instance in Rust).

let mut inputs = Layer::new(SAMPLE_SIZE as usize, SAMPLE_SIZE as usize);

But when I call this save_as_ppm function:

inputs.save_as_ppm(&filepath)

It compiles. My question is why does it compile? save_as_ppm takes a reference to self but I've just passed a mutable self. Shouldn't the compiler give an error? At least a warning?

E_net4
  • 27,810
  • 13
  • 101
  • 139
merovingian
  • 515
  • 2
  • 9
  • 1
    The compiler is allowed to automatically cast `&mut T` to `&T`, just not the other way around. – PitaJ Aug 17 '22 at 15:21
  • 1
    Following the rules of duck typing, since a mutable reference has all the features + more compared to an immutable reference, I see no reason why you shouldn't be able to pass in a mutable reference since the function itself won't be able to mutate the state either way – Samathingamajig Aug 17 '22 at 15:22
  • 1
    A method that needs a shared reference will happily accept a mutable one, because it's always safe to "downgrade" a mutable reference to a shared one. It would indeed be annoying if `let mut v = vec![]; v.push(1); println!("{}", v.len())` wouldn't compile because `Vec::len()` takes `&self`, but `v` is mut. – user4815162342 Aug 17 '22 at 15:23
  • Function won't be able to mutate the state because the signature is immutable? @Samathingamajig – merovingian Aug 17 '22 at 15:27
  • Since Rust compiler is very pedantic I thought it wouldn't like this kind of casting at all – merovingian Aug 17 '22 at 15:27
  • 1
    @merovingian Yeah the function won't be able to mutate the state since its signature is for an immutable reference. Trying to mutate the state will give a compiler error. – Samathingamajig Aug 17 '22 at 15:31
  • @merovingian Rust is generally pretty reserved on implicit coercions but [they do exist](https://doc.rust-lang.org/reference/type-coercions.html). In this case, the coercion is both harmless and it would significantly hinder readability if it didn't have auto-referencing on method calls. – kmdreko Aug 17 '22 at 15:33
  • 2
    Also note that there is a different between a reference to a mutable value & a mutable reference – Samathingamajig Aug 17 '22 at 15:46

1 Answers1

4

save_as_ppm takes a reference to self but I've just passed a mutable self.

This is not what's happening here. A method call expression uses a resolution algorithm which enables users to call a method without having to explicitly create a suitable borrow of the receiving value. If necessary, the borrow effectively made will match the requested receiving type.

In other words, the code

inputs.save_as_ppm(&filepath)

is equivalent to this call in fully qualified syntax:

Layer::save_as_ppm(&inputs, &filepath)

Note that an immutable borrow was made directly: there is no conversion between different kinds of borrows, and all of this is inconsequential to the mutability of the binding (let mut inputs). Since both immutable and mutable variable bindings can be immutably borrowed, the code complies with the borrow checker and compiles.

As an addendum, nothing would stop you from really passing a mutable reference:

Layer::save_as_ppm(&mut inputs, &filepath);

This works because the mutable reference is coerced into an immutable reference with the same lifetime. It is one of the enumerated type coercions available in Rust.

See also:

E_net4
  • 27,810
  • 13
  • 101
  • 139