Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between &mut and ref mut for trait objects

First of all, I'm not asking what's the difference between &mut and ref mut per se.

I'm asking because I thought:

let ref mut a = MyStruct

is the same as

let a = &mut MyStruct

Consider returning a trait object from a function. You can return a Box<Trait> or a &Trait. If you want to have mutable access to its methods, is it possible to return &mut Trait?

Given this example:

trait Hello {
    fn hello(&mut self);
}

struct English;
struct Spanish;

impl Hello for English {
    fn hello(&mut self) {
        println!("Hello!");
    }
}

impl Hello for Spanish {
    fn hello(&mut self) {
        println!("Hola!");
    }
}

The method receives a mutable reference for demonstration purposes.

This won't compile:

fn make_hello<'a>() -> &'a mut Hello {
    &mut English
}

nor this:

fn make_hello<'a>() -> &'a mut Hello {
    let b = &mut English;
    b
}

But this will compile and work:

fn make_hello<'a>() -> &'a mut Hello {
    let ref mut b = English;
    b
}

My theory

This example will work out of the box with immutable references (not necessary to assign it to a variable, just return &English) but not with mutable references. I think this is due to the rule that there can be only one mutable reference or as many immutable as you want.

In the case of immutable references, you are creating an object and borrowing it as a return expression; its reference won't die because it's being borrowed.

In the case of mutable references, if you try to create an object and borrow it mutably as a return expression you have two mutable references (the created object and its mutable reference). Since you cannot have two mutable references to the same object it won't perform the second, hence the variable won't live long enough. I think that when you write let mut ref b = English and return b you are moving the mutable reference because it was captured by a pattern.

All of the above is a poor attempt to explain to myself why it works, but I don't have the fundamentals to prove it.

Why does this happen?

I've also cross-posted this question to Reddit.

like image 249
Rodolfo Avatar asked Dec 06 '17 17:12

Rodolfo


1 Answers

This is a bug. My original analysis below completely ignored the fact that it was returning a mutable reference. The bits about promotion only make sense in the context of immutable values.


This is allowable due to a nuance of the rules governing temporaries (emphasis mine):

When using an rvalue in most lvalue contexts, a temporary unnamed lvalue is created and used instead, if not promoted to 'static.

The reference continues:

Promotion of an rvalue expression to a 'static slot occurs when the expression could be written in a constant, borrowed, and dereferencing that borrow where the expression was the originally written, without changing the runtime behavior. That is, the promoted expression can be evaluated at compile-time and the resulting value does not contain interior mutability or destructors (these properties are determined based on the value where possible, e.g. &None always has the type &'static Option<_>, as it contains nothing disallowed).

Your third case can be rewritten as this to "prove" that the 'static promotion is occurring:

fn make_hello_3<'a>() -> &'a mut Hello {
    let ref mut b = English;
    let c: &'static mut Hello = b;
    c
}

As for why ref mut allows this and &mut doesn't, my best guess is that the 'static promotion is on a best-effort basis and &mut just isn't caught by whatever checks are present. You could probably look for or file an issue describing the situation.

like image 103
Shepmaster Avatar answered Oct 21 '22 07:10

Shepmaster