rustimmutabilitydereferenceborrow-checkerrust-clippy

What is the difference between "reborrow"-ing and "deref"-ing?


Clippy is warning me about a "deref on an immutable reference" in my code, an example being &*s where s is a &String.

Clippy suggests,

if you would like to reborrow, try removing &*

or

if you would like to deref, try using &**

What is the difference between these two suggestions?


Solution

  • The first thing to understand is that Deref is a trait. If T: Deref<Target=U>, it means that T can be seen as a pointer to U (roughly). This entails that Rust will automatically transform a &T into a &U if it needs to (ie. if you are calling a method of U on a value of type T). This also triggers when you try to deference T: Rust will deref it into U.

    However, this is not the case here: you are dereferencing &String, which will give a String (without even thinking about String as a pointer to str, because String: Deref<Target=str>), then you borrow that thing again. If your objective was to end up with a &str, you failed. Which is why Rust proposes to to deference once again: &** &String -> &* String -> &* &str -> & str -> &str.


    Or, maybe you wanted to do something called reborrow, which has nothing to do with dereferencing. Reborrow is needed when you have a certain kind of borrow, and you need an other kind of borrow. For instance, if you have a &mut T and wanted a &T, you could reborrow. Note that a reborrow always specifically means dereferencing a borrow, just to borrow it again: it will not trigger a deref or anything like that, it's just a type-checking step (in practice, a reborrow won't produce any code, it's just for the compiler to reason about; whereas a deref will produce some code).

    An example of why you would want to do that is to change the implicit lifetime bounded to a borrow: when you reborrow, you are authorized to have a give smaller lifetime to the new borrow, which allows you to reuse the original borrow once you're done with the reborrowed one. See an example provided by @Cerberus on the playground:

    use core::fmt::Debug;
    
    fn debug(val: impl Debug) {
        dbg!(val);
    }
    
    fn debug_ref(val: &impl Debug) {
        dbg!(val);
    }
    
    fn main() {
        let mut x = 42;
        let x_ref = &mut x;
        
        // fails to compile - `x_ref` is moved into `debug`,
        // but later we use it again
        // debug(x_ref);
        
        // compiles successfully
        debug(&mut *x_ref);
        
        // also compiles successfully, since the target type is reference,
        // so `x_ref` is implicitly reborrowed
        debug_ref(x_ref);
        
        *x_ref = 84;
        debug(x_ref);
    }
    

    However, in your case, you would transform a regular borrow (that Clippy calls "immutable borrow") into a regular borrow: you're doing nothing! Which is why Clippy suggests that you remove that altogether.