-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[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: master
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)] | ||
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 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, | ||
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:
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 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. 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 🤔 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 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 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 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 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 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>>; | ||
|
||
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 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 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.