- Notifications
You must be signed in to change notification settings - Fork 14k
[WIP] Add downcast_trait and downcast_trait_mut #144363
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
base: main
Are you sure you want to change the base?
Changes from all commits
ca8e62d 4a00305 17b5402 d40408e 7a46b59 46224d5 b16ea15 ff13405 93aa4f2 1249f38 ad5fa57 0da23e7 d9b8269 b08d57f db579d3 852c332 8298c84 d9cb965 2919430 8e9b17b 5b438f8 11748bf c38ca5b File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -2350,6 +2350,7 @@ symbols! { | |
| vreg_low16, | ||
| vsx, | ||
| vtable_align, | ||
| vtable_for, | ||
| vtable_size, | ||
| warn, | ||
| wasip2, | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -86,7 +86,7 @@ | |
| | ||
| #![stable(feature = "rust1", since = "1.0.0")] | ||
| | ||
| use crate::{fmt, hash, intrinsics}; | ||
| use crate::{fmt, hash, intrinsics, ptr}; | ||
| | ||
| /////////////////////////////////////////////////////////////////////////////// | ||
| // Any trait | ||
| | @@ -896,3 +896,45 @@ pub const fn type_name<T: ?Sized>() -> &'static str { | |
| pub const fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str { | ||
| type_name::<T>() | ||
| } | ||
| | ||
| #[allow(missing_docs)] | ||
| #[must_use] | ||
| #[unstable(feature = "downcast_trait", issue = "144361")] | ||
| pub const fn downcast_trait< | ||
| T: Any + 'static, | ||
ivarflakstad marked this conversation as resolved. Show resolved Hide resolved | ||
| U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized + 'static, | ||
| >( | ||
| t: &T, | ||
| ) -> Option<&U> { | ||
| let vtable: Option<ptr::DynMetadata<U>> = const { intrinsics::vtable_for::<T, U>() }; | ||
| match vtable { | ||
| Some(dyn_metadata) => { | ||
| let pointer = ptr::from_raw_parts(t, dyn_metadata); | ||
| // SAFETY: `t` is a reference to a type, so we know it is valid. | ||
| // `dyn_metadata` is a vtable for T, implementing the trait of `U`. | ||
| Some(unsafe { &*pointer }) | ||
ivarflakstad marked this conversation as resolved. Show resolved Hide resolved | ||
| } | ||
| None => None, | ||
| } | ||
| } | ||
| | ||
| #[allow(missing_docs)] | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need some docs before merging Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely. Just appeasing the commit hook | ||
| #[must_use] | ||
| #[unstable(feature = "downcast_trait", issue = "144361")] | ||
| pub const fn downcast_trait_mut< | ||
| T: Any + 'static, | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does the We'll need some ui tests that actually error when Some general test ideas:
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also: Test out:
This is also I think still unsound because we don't (and cannot) enforce that the lifetime in the input and output are equal. This should allow you to unsoundly cast Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 'static bound maybe prevents potential unsoundness. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, then that really limits the usefulness of this intrinsic as an alternative to specialization as noted in the tracking issue 🤔 Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As @oli-obk noted in another comment the intrinsic doesn't necessarily need Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for clarity @RalfJung I just plopped it in Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding it to unresolved questions in the tracking issue Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conceptually yes, but in our case Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But perhaps the naming change we've been discussing in the tracking issue is enough. | ||
| U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized + 'static, | ||
| >( | ||
| t: &mut T, | ||
| ) -> Option<&mut U> { | ||
| let vtable: Option<ptr::DynMetadata<U>> = const { intrinsics::vtable_for::<T, U>() }; | ||
| match vtable { | ||
| Some(dyn_metadata) => { | ||
| let pointer = ptr::from_raw_parts_mut(t, dyn_metadata); | ||
| // SAFETY: `t` is a reference to a type, so we know it is valid. | ||
| // `dyn_metadata` is a vtable for T, implementing the trait of `U`. | ||
| Some(unsafe { &mut *pointer }) | ||
| } | ||
| None => None, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -2657,6 +2657,18 @@ pub unsafe fn vtable_size(ptr: *const ()) -> usize; | |
| #[rustc_intrinsic] | ||
| pub unsafe fn vtable_align(ptr: *const ()) -> usize; | ||
| | ||
| /// FIXME: write actual docs (ivarflakstad) | ||
| /// The intrinsic will return the vtable of `t` through the lens of `U`. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// `ptr` must point to a vtable. | ||
| #[rustc_nounwind] | ||
| #[unstable(feature = "core_intrinsics", issue = "none")] | ||
| #[rustc_intrinsic] | ||
| pub const fn vtable_for<T, U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized>() | ||
| -> Option<ptr::DynMetadata<U>>; | ||
| | ||
| Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you replaced the erased lifetimes with Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could make it unsafe to allow for best-effort checks for optimizations like the Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I think it doesn't need to be as long as it's documented what returning Some means | ||
| /// The size of a type in bytes. | ||
| /// | ||
| /// Note that, unlike most intrinsics, this is safe to call; | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| //@ run-pass | ||
| #![feature(downcast_trait)] | ||
| | ||
| use std::fmt::Debug; | ||
| | ||
| // Look ma, no `T: Debug` | ||
| fn downcast_debug_format<T: 'static>(t: &T) -> String { | ||
| match std::any::downcast_trait::<_, dyn Debug>(t) { | ||
| Some(d) => format!("{d:?}"), | ||
| None => "default".to_string() | ||
| } | ||
| } | ||
| | ||
| // Test that downcasting to a dyn trait works as expected | ||
| fn main() { | ||
| #[allow(dead_code)] | ||
| #[derive(Debug)] | ||
| struct A { | ||
| index: usize | ||
| } | ||
| let a = A { index: 42 }; | ||
| let result = downcast_debug_format(&a); | ||
| assert_eq!("A { index: 42 }", result); | ||
| | ||
| struct B {} | ||
| let b = B {}; | ||
| let result = downcast_debug_format(&b); | ||
| assert_eq!("default", result); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| //@ run-pass | ||
| #![feature(downcast_trait)] | ||
| | ||
| use std::{any::downcast_trait, sync::OnceLock}; | ||
| | ||
| trait Trait { | ||
| fn call(&self, x: &Box<i32>); | ||
| } | ||
| | ||
| impl Trait for for<'a> fn(&'a Box<i32>) { | ||
| fn call(&self, x: &Box<i32>) { | ||
| self(x); | ||
| } | ||
| } | ||
| | ||
| static STORAGE: OnceLock<&'static Box<i32>> = OnceLock::new(); | ||
| | ||
| fn store(x: &'static Box<i32>) { | ||
| STORAGE.set(x).unwrap(); | ||
| } | ||
| | ||
| fn main() { | ||
| let data = Box::new(Box::new(1i32)); | ||
| let fn_ptr: fn(&'static Box<i32>) = store; | ||
| let dt = downcast_trait::<_, dyn Trait>(&fn_ptr); | ||
| if let Some(dt) = dt { | ||
| // unsound path | ||
| dt.call(&*data); | ||
| drop(data); | ||
| println!("{}", STORAGE.get().unwrap()); | ||
| } else { | ||
| println!("success") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| //@ run-pass | ||
| #![feature(downcast_trait)] | ||
| use std::{any::downcast_trait, sync::OnceLock}; | ||
| | ||
| trait Trait<T> { | ||
| fn call(&self, t: T, x: &Box<i32>); | ||
| } | ||
| | ||
| impl Trait<for<'a> fn(&'a Box<i32>)> for () { | ||
| fn call(&self, f: for<'a> fn(&'a Box<i32>), x: &Box<i32>) { | ||
| f(x); | ||
| } | ||
| } | ||
| | ||
| static STORAGE: OnceLock<&'static Box<i32>> = OnceLock::new(); | ||
| | ||
| fn store(x: &'static Box<i32>) { | ||
| STORAGE.set(x).unwrap(); | ||
| } | ||
| | ||
| fn main() { | ||
| let data = Box::new(Box::new(1i32)); | ||
| let dt = downcast_trait::<_, dyn Trait<fn(&'static Box<i32>)>>(&()); | ||
| if let Some(dt) = dt { | ||
| // unsound path | ||
| dt.call(store, &*data); | ||
| drop(data); | ||
| println!("{}", STORAGE.get().unwrap()); | ||
| } else { | ||
| println!("success") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| //@ run-pass | ||
| #![feature(downcast_trait)] | ||
| | ||
| use std::fmt::{Error, Write}; | ||
| | ||
| // Look ma, no `T: Write` | ||
| fn downcast_mut_write<T: 'static>(t: &mut T, s: &str) -> Result<(), Error> { | ||
| match std::any::downcast_trait_mut::<_, dyn Write>(t) { | ||
| Some(w) => w.write_str(s), | ||
| None => Ok(()) | ||
| } | ||
| } | ||
| | ||
| // Test that downcasting to a mut dyn trait works as expected | ||
| fn main() { | ||
| let mut buf = "Hello".to_string(); | ||
| | ||
| downcast_mut_write(&mut buf, " world!").unwrap(); | ||
| assert_eq!(buf, "Hello world!"); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.