Does compiler know about UnsafeCell?

Or why is this code compiled

fn main() {
    struct Item<T> {
        value: MyUnsafeCell<T>,
    }

    struct MyUnsafeCell<T: ?Sized> {
        value: T,
    }

    fn fun<'a>(item: Item<&'a str>) {
        let str = "some_str".to_string();
        let str = str.as_str();
        inner_fun(str, item);
    }

    fn inner_fun<T>(_t: T, _item: Item<T>) {}
}

but this code is not?

fn main() {
    struct Item<T> {
        value: std::cell::UnsafeCell<T>,
    }

    fn fun<'a>(item: Item<&'a str>) {
        let str = "some_str".to_string();
        let str = str.as_str();
        inner_fun(str, item);
    }

    fn inner_fun<T>(_t: T, _item: Item<T>) {}
}

Also can someone explain what means

    inner_fun(str, item);
    -------------------- argument requires that `str` is borrowed for `'a`

in the second case? And why "argument requires that str is borrowed for 'a" in the second case?

Thank you

UnsafeCell is a "magic" language-level struct. But the reason for the error isn't its magical properties, it's the fact that it is invariant in T instead of covariant. If you make your struct invariant in T (e.g. by using PhantomData), it will result in the borrow check error too.

"Invariant" here means that Item<&'long str> cannot coerce to Item<&'short str> for example.

Lifetime parameters on functions are always at least just-longer than the function body, so you can never borrow a local variable for as long as such a lifetime. That's what you're trying to do in the invariant case: borrow your str: String for 'a.

    fn fun<'a>(item: Item<&'a str>) {
        let str = "sg".to_string();
        // Need to borrow for `'a`...
        let str = str.as_str();
        // ...so that you can pass a `T = &'a str` here
        inner_fun(str, item);
    }

It doesn't error in the covariant case because the Item<&'a str> can coerce to a Item<&'local str>, where 'local ends after the call to inner_fun.

7 Likes

In particular:

  • You cannot emulate its effects using any struct not itself containing an UnsafeCell.
  • The way this is marked in the source code of the standard library is the attribute #[lang = "unsafe_cell"]. Any time you see #[lang], something special is going on.[1]

  1. Though it might be as innocuous as the fact that for loops generate calls to the Iterator trait, so Iterator is a lang item. ↩︎

7 Likes

It's great answer! Thanks for the rapid reaction and a detailed explanation
But

is it mistake? Does PhantomData make my struct invariant? According to Subtyping and Variance - The Rust Reference PhantomData is covariant over T

And for checking yourself

In second case my item value is implicit coerced to a Item<&'local str> type before inner_fun call. Is it correct?

Thanks for the clarification
I wanted to ask this question, but you answered before I asked

Indeed. But PhantomData lets you use an invariant (or contravariant) type without needing a value of that type.[1]

So, to make something invariant in T, you can use PhantomData<fn(T) -> T>, because fn(T) -> T is invariant in T.


  1. The covariance of PhantomData only means that its variance directly mirrors the variance of its type parameter. ↩︎

1 Like

Check out the table of patterns in the link.

Correct. If you prevent the coercion via turbofish, you'll also see the error again.

        inner_fun::<&'a str>(str, item);
1 Like