If I hold shared refs, but can promise the compiler that I am uniquely borrowing, can I upgrade my ref (without unsafe)?

Hi!

I think the code example explains itself well; have a look!

#[derive(Clone)]
struct Foo(String);

struct FooRef<'a>(&'a Foo);

fn returns_a() -> Vec<Foo> {
    let a = Foo("a".into());
    let b = Foo("b".into());
    let c = Foo("c".into());
    
    let mut foos = vec![FooRef(&a), FooRef(&b), FooRef(&c)];
    
    // We hold immutable refs, to semmantically signal that the following
    // section of code must not mutate.
    
    // ...
    let foos = foos
        .iter()
        // Must be equal to "a":
        .filter(|t| t.0.0 == "a");
    // ...
    
    // Finally We have our "winning" Foos and now would like move them out
    // without cloning...
    foos
        .map(|t| t.0)
        .cloned()
        .collect()
}

An idiomatic approach would be along the lines of

#[derive(Clone)]
struct Foo(String);

fn returns_a() -> Vec<Foo> {
    let a = Foo("a".into());
    let b = Foo("b".into());
    let c = Foo("c".into());
    
    let foos = [a, b, c];
    
    // ...
    foos
        .into_iter()
        // Must be equal to "a":
        .filter(|t| t.0 == "a")
        .collect()
}

The mutability of the collection is largely irrelevant if the predicate promises not to mutate its arguments (as this one does), and especially irrelevant if the collection will be consumed anyways.

You cannot upgrade an &Foo ref to &mut Foo. If you can promise that the refs are unique to start with, then you can communicate that promise to the compiler by using &mut Foo.

4 Likes

No, Rust doesn't provide any way to define "unique immutable references". The concept exists internally in the compiler, but you can't use it.

It's also a misnormer to call them "shared references", they really are "immutable references". That's the part of the talk when someone goes "no, they are really 'shared', because &UnsafeCell<T> can be mutated". Right. You're not holding any UnsafeCell here, and in the absence of it contents of immutable references cannot be mutated, full stop. Trying to circumvent it in any way is Undefined Behaviour.

Why do you even keep around references? If you have unique ownership, you can just pass objects directly by value.

Right, obviously my example is a simplification. Let's say I am forced to work with references, is there a way I could be sneaky about it, without, say carrying around a vec of indexxes or something

Can you show some of the details you've simplified away, to help us better understand the actual constraints of your program? The trouble with "let's say that" is that it invites unhelpfully vague answers, like "refactor the code not to have that requirement" (my instinct, in point of fact). I'd much rather talk through the specifics with you than wave my hands.

1 Like

From the nomicon:

  • Transmuting an & to &mut is always Undefined Behavior.
  • No you can't do it.
  • No you're not special.
4 Likes