rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.61k stars 12.74k forks source link

Tracking Issue for `#![feature(downcast_unchecked)]` #90850

Open ibraheemdev opened 3 years ago

ibraheemdev commented 3 years ago

Feature gate: #![feature(downcast_unchecked)]

This is a tracking issue for the downcast_unchecked, downcast_ref_unchecked, and downcast_mut_unchecked methods.

Public API

impl dyn Any (+ Send + Sync) {
    pub unsafe fn downcast_ref_unchecked<T: Any>(&self) -> &T;
    pub unsafe fn downcast_mut_unchecked<T: Any>(&mut self) -> &mut T;
}

impl<A: Allocator> Box<dyn Any (+ Send + Sync), A> {
    pub unsafe fn downcast_unchecked<T: Any>(&self) -> Box<T, A>;
}

Steps / History

Unresolved Questions

david-perez commented 2 years ago

Should these be added to std::error::Error too?

ibraheemdev commented 2 years ago

I don't see how that would be useful. You generally don't know the concrete type of an error before downcasting.

lunabunn commented 2 years ago

This would allow me to shed my dependency on unsafe_any. What's the state of this feature? Is it ready for an FCP?

ibraheemdev commented 2 years ago

Now that {Option, Result}::unwrap_unchecked are stabilized, I wonder if there is much benefit to these methods.

CAD97 commented 2 years ago

Instead of downcast_unchecked, is there a good reason to not just have the more general

impl Box<T: ?Sized, A> {
    pub unsafe fn cast_unchecked<U>(self) -> Box<U, A> {
        debug_assert_eq!(Layout::for_value(&*self), Layout::new::<U>());
        unsafe {
            let (raw, alloc) = Box::into_raw_with_allocator(self);
            Box::from_raw_in(raw.cast(), alloc)
        }
    }
}

? I suppose you lose the debug_assert! for correct typing which is possible if you have dyn Any, though it is true that you can already any.downcast().unwrap_unchecked() for that.

To immediately answer my own question: Box doesn't want to add methods to arbitrary types like that. cast_unchecked is still useful but would probably instead be pub unsafe fn Box::<T: ?Sized, A>::cast<U>(this: Self) -> Box<U, A> to make it an associated function rather than a method which could shadow a pointee's method.

yshui commented 2 years ago

@ibraheemdev this still generates better code than downcast_ref().unwrap_unchecked().

pub fn cast(a: &dyn std::any::Any) -> &i32 {
    unsafe { a.downcast_ref_unchecked() }
}

compiles to

example::cast:
        mov     rax, rdi
        ret

whereas

pub fn cast(a: &dyn std::any::Any) -> &i32 {
    unsafe { a.downcast_ref().unwrap_unchecked() }
}

becomes

example::xx:
        push    rbx
        mov     rbx, rdi
        call    qword ptr [rsi + 24]
        mov     rax, rbx
        pop     rbx
        ret

Both compiled with opt-level=3

yshui commented 2 years ago

I think llvm didn't realize that T::type_id call is pure (because it's called through the vtable?), so it kept the call.

yshui commented 2 years ago

huh, why is type_id not stored directly in the vtable?

CAD97 commented 2 years ago

huh, why is type_id not stored directly in the vtable?

The short version is that Rust doesn't have a way to get associated constant data from a trait object (yet).

gh2o commented 1 year ago

The semantics of a.downcast_ref_unchecked() and a.downcast_ref().unwrap_unchecked() are still different. For example, with a.downcast_ref_unchecked() one could "transmute" between two types that are different but #[repr(C)] compatible in layout, whereas with a.downcast_ref().unwrap_unchecked() you'd likely be dereferencing a null pointer.

marc0246 commented 1 year ago

I'd like to point out that in all other places in the std, the ref/mut part comes after the unchecked part. Therefore this should be downcast_unchecked_ref and downcast_unchecked_mut to stay concistent AFAICS.

Wasabi375 commented 4 months ago

Hey, is there a reason that Box::downcast and similar functions require T to be sized? AFAICT they should work with unsized types.

marc0246 commented 4 months ago

@Wasabi375 It's not possible to unsize an unsized type, so it's equally not possible to downcast to an unsized type, because the source is already unsized. If you want to do something like that you need a double indirection.

Wasabi375 commented 4 months ago

I think I might have found a bug with with type inference or something that confused me, because it claimed that I couldn't call downcast_unchecked because dyn Any is not sized. But when I specify the type directly the error goes away. I'll try to create a simple example and file a proper bug report. (#127005)

IamTheCarl commented 4 months ago

I was curious if the code quality generated by unwrap_unchecked vs downcast_ref_unchecked had improved at all. It seems it has.

https://rust.godbolt.org/z/77E7an7f1

Note that it seems to be condensing both functions to one since they're end product is identical. You can comment out the first one to explicitly see that the second one is equivalent.