Is this error due to the compiler's special knowledge about RefCell?

前端 未结 1 2072
北海茫月
北海茫月 2020-12-17 19:28
fn works<\'a>(foo: &Option<&\'a mut String>, s: &\'a mut String) {}
fn error<\'a>(foo: &RefCell

        
相关标签:
1条回答
  • 2020-12-17 19:58

    RefCell<T> contains an UnsafeCell<T> which is a special lang item. It is UnsafeCell that causes the error. You could check with:

    fn error<'a>(foo: &UnsafeCell<Option<&'a mut String>>, s: &'a mut String) {}
    
    ...
    
    let bar = UnsafeCell::new(None);
    error(&bar, &mut s);
    

    But the error is not due to compiler recognizing an UnsafeCell introduces interior mutability, but that an UnsafeCell is invariant in T. In fact, we could reproduce the error using PhantomData:

    struct Contravariant<T>(PhantomData<fn(T)>);
    
    fn error<'a>(foo: Contravariant<&'a i32>, s: &'a mut String) {}
    
    ...
    
    let bar = Contravariant(PhantomData);
    error(bar, &mut s);
    

    or even just anything that is contravariant or invariant in the lifetime 'a:

    fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {}
    
    let bar = None;
    error(bar, &mut s);
    

    The reason you can't hide a RefCell is because variance is derived through the fields of the structure. Once you used RefCell<T> somewhere, no matter how deep, the compiler will figure out T is invariant.


    Now let's see how the compiler determine the E0502 error. First, it's important to remember that the compiler has to choose two specific lifetimes here: the lifetime in the type of the expression &mut s ('a) and the lifetime in the type of bar (let's call it 'x). Both are restricted: the former lifetime 'a has to be shorter than the scope of s, otherwise we would end up with a reference living longer than the original string. 'x has to be larger than the scope of bar, otherwise we could access an dangling pointer through bar (if a type has a lifetime parameter the compiler assume the type can access a value with that lifetime).

    With these two basic restriction, the compiler goes through the following steps:

    1. The type bar is Contravariant<&'x i32>.
    2. The error function accepts any subtype of Contravariant<&'a i32>, where 'a is the lifetime of that &mut s expression.
    3. Thus bar should be a subtype of Contravariant<&'a i32>
    4. Contravariant<T> is contravariant over T, i.e. if U <: T, then Contravariant<T> <: Contravariant<U>.
    5. So the subtyping relation can be satisfied when &'x i32 is a supertype of &'a i32.
    6. Thus 'x should be shorter than 'a, i.e. 'a should outlive 'x.

    Similarly, for an invariant type, the derived relation is 'a == 'x, and for convariant, 'x outlives 'a.

    Now, the problem here is that the lifetime in the type of bar lives until the end of scope (as per restriction mentioned above):

        let bar = Contravariant(PhantomData);   // <--- 'x starts here -----+
        error(bar,                              //                          |
              &mut s);                          // <- 'a starts here ---+   |
        s.len();                                //                      |   |
                                                // <--- 'x ends here¹ --+---+
                                                //                      |
                                                // <--- 'a ends here² --+
    }
    
    // ¹ when `bar` goes out of scope
    // ² 'a has to outlive 'x
    

    In both contravariant and invariant cases, 'a outlives (or equals to) 'x means the statement s.len() must be included in the range, causing borrowck error.

    Only in the covariant case we could make the range of 'a shorter than 'x, allowing the temporary object &mut s be dropped before s.len() is called (meaning: at s.len(), s is not considered borrowed anymore):

        let bar = Covariant(PhantomData);       // <--- 'x starts here -----+
                                                //                          |
        error(bar,                              //                          |
              &mut s);                          // <- 'a starts here --+    |
                                                //                     |    |
                                                // <- 'a ends here ----+    |
        s.len();                                //                          |
    }                                           // <--- 'x ends here -------+
    
    0 讨论(0)
提交回复
热议问题