Skip to content

Remove object lifetime cast #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 23, 2025
Merged

Conversation

BoxyUwU
Copy link
Contributor

@BoxyUwU BoxyUwU commented Mar 13, 2025

Hi there o/

I'm a member of the Rust Language Types Team; as part of stabilizing the arbitrary_self_types and derive_coerce_pointee features we may need to change what raw pointer casts are legal (rust-lang/rust#136702). Specifically, casting *const dyn Trait + 'a to *const dyn Trait + 'b where it is not able to be proven that 'a outlives 'b may become an error.

Without going into too much detail, the justification for this change be that casting trait object's lifetime bound to a lifetime that lives for a longer time could invalidate the VTable of the trait object, allowing for dispatching to methods that should not be callable. See this example from the linked issue:

#![forbid(unsafe_code)]
#![feature(arbitrary_self_types, derive_coerce_pointee)]

use std::any::TypeId;
use std::marker::{CoercePointee, PhantomData};

#[derive(CoercePointee)]
#[repr(transparent)]
struct SelfPtr<T: ?Sized>(*const T);

impl<T: ?Sized> std::ops::Deref for SelfPtr<T> {
    type Target = T;
    fn deref(&self) -> &T {
        panic!("please don't call me, I just want the `Receiver` impl!");
    }
}

trait GetTypeId {
    fn get_type_id(self: SelfPtr<Self>) -> TypeId
    where
        Self: 'static;
}

impl<T: ?Sized> GetTypeId for PhantomData<T> {
    fn get_type_id(self: SelfPtr<Self>) -> TypeId
    where
        Self: 'static,
    {
        TypeId::of::<T>()
    }
}

// no `T: 'static` bound necessary
fn type_id_of<T: ?Sized>() -> TypeId {
    let ptr = SelfPtr(
        &PhantomData::<T> as *const (dyn GetTypeId + '_) as *const (dyn GetTypeId + 'static),
    );
    ptr.get_type_id()
}

Unfortunately, going through the usual "future compatibility warning" process may turn out to not be possible as checking lifetime constraints without emitting an error is prohibitively difficult to do in the implementation of the borrow checker. This means that you may wind up never receiving a FCW and its associated grace period to update your code to be compatible with the new rules.

I have attempted to update your codebase to the new rules for you so that it will still compile if we make this change. I hope this potential breakage won't cause you too much trouble and that you might even find the post-migration code to be better than how it was previously :-)

Finally, it has not been decided yet whether we are to go through with this breaking change. If you feel that your code should continue to work as-is then please let me know and we'll try to take that into account when evaluating whether to go through with this breakage. Additionally if you have any questions about why this change is required please let me know and I'll do my best to further explain the justification.

@WorldSEnder
Copy link
Owner

WorldSEnder commented Mar 13, 2025

This seems reasonable. Are there any plans for change the ownership assumptions I make over the pointer metadata? Let me explain:

The current code basically wants to cast Arc<dyn T + 'a> where 'a is some local lifetime to Arc<dyn T + 'static> and assumes that the latter will have access to the vtable for the however long it wants to. While I don't think the linked issue changes this, I want to make sure this is still the case.

Further, the linked issue definitely shows behaviour that shouldn't be possible in safe Rust, but since my code already wraps it in unsafe, are there plans to allow this sort of cast in unsafe as is? Using transmute always feels like bringing a chainsaw just to cut some cheese on the kitchen table.

Thanks for the heads up in any case!

@BoxyUwU
Copy link
Contributor Author

BoxyUwU commented Mar 21, 2025

I don't believe anything is changing here in a way that affects the underlying concept of what you're trying to do here (i.e, if it was correct before it will continue to be correct). In general you just have to be careful when unsafely extending the lifetime of the trait object that you not expose it to safe code that expects the lifetime bound to be "truthful".

The VTable in some sense is "valid" at any point regardless of the lifetime bound, or in otherwords, VTables don't contain potentially dangling references and it effectively lives for 'static. This is true of both current stable and also in the future world where casting object lifetimes requires unsafe. The only sense in which it becomes "invalid" is that some lifetime bounds on functions may have become satisfiable after your lifetime cast where previously they would not have been (which may lead to functions being callable that should not be).

I don't think its been brought up yet in discussion as to whether to retain the current behaviour inside of a unsafe contexts. I'll make sure that's considered before any breakage makes its way to stable, though I can't guarantee anything here of course :)

I do agree that transmute feels very heavy handed though and it's unfortunate that this is the only migration strategy currently (though once again I can't guarantee there being a different path forwards).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants