我们在阅读底层的代码过程中,经常会看*const ()和fn(*const ()) 之类的,那这个指的是什么?
一、以Bytes库为例
在Bytes库在github源码(https://docs.rs/bytes/1.1.0/src/bytes/bytes.rs.html#94-100)有关Bytes的定义中,
pub struct Bytes {
ptr: *const u8,
len: usize, // inlined "trait object"
data: AtomicPtr<()>,
vtable: &'static Vtable,
}
其中的data字段中有(),一般地,()表示一个空元组。但在这里,是不是这个意思?
我们看到,data可以用这个来赋值:
AtomicPtr::new(ptr::null_mut())
那ptr::null_mut()又是啥?查看https://doc.rust-lang.org/stable/std/ptr/fn.null_mut.html,可以看到:
pub const fn null_mut<T: ?Sized + Thin>() -> *mut T {
from_raw_parts_mut(invalid_mut(0), ())
}
//再查invaild_mut
pub const fn invalid_mut<T>(addr: usize) -> *mut T {
// FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic.
// We use transmute rather than a cast so tools like Miri can tell that this
// is *not* the same as from_exposed_addr.
// SAFETY: every valid integer is also a valid pointer (as long as you don't dereference that
// pointer).
unsafe { mem::transmute(addr) }
}
表示:Creates a null mutable raw pointer.类似,创建一个不变的raw pointer有,
pub const fn null<T>() -> *const T
where
T: Thin + ?Sized,
进而,查看https://doc.rust-lang.org/src/core/sync/atomic.rs.html#175,可以了解AtomicPtr的定义,
pub struct AtomicPtr<T> {
p: UnsafeCell<*mut T>,
}
也就是可以了解,Bytes中的data字段是一个用AtomicPtr封装起来的一个函数的可变raw pointer.
当然,上面的data还可以这样赋值:
// slice是Box<[u8]>
//let ptr = Box::into_raw(slice) as *mut u8;
//let data = ptr as usize
data: AtomicPtr::new(data as *mut _),
另外,*const ()也自然可以联想到,它可能是表示指向一个固定的函数的raw pointer.
通过下面例子,也可以看到,*const ()与函数指针的转化。
fn foo(i:i32) -> i32 {
666666
}
fn bar() -> String{
println!("i am bar!");
String::from("bar")
}
fn main(){
let ptr_1 = foo as *const ();
let ptr_2 = bar as *const ();
let fn_1 = unsafe {
std::mem::transmute::<*const (), fn(i32) -> i32>(ptr_1)
};
let fn_2 = unsafe {
std::mem::transmute::<*const (), fn() ->String >(ptr_2)
};
// transmute的另一种表示方法,但需要类型显示前置标明
let fn_3 : fn() ->String = unsafe {
std::mem::transmute(ptr_2)
};
assert_eq!(fn_1(42), 666666);
assert_eq!(fn_2(),String::from("bar"));
assert_eq!(fn_3(),String::from("bar"));
}
二、以Future为例
类似的,我们在rust异步Future相关代码中,这种*const ()则可以在底层同样看到:
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
#[stable(feature = "futures_api", since = "1.36.0")]
#[lang = "Context"]
pub struct Context<'a> {
waker: &'a Waker,
_marker: PhantomData<fn(&'a ()) -> &'a ()>,
_marker2: PhantomData<*mut ()>,
}
#[stable(feature = "futures_api", since = "1.36.0")]
pub struct Waker {
waker: RawWaker,
}
pub struct RawWaker {
data: *const (),
vtable: &'static RawWakerVTable,
}
pub struct RawWakerVTable {
clone: unsafe fn(*const ()) -> RawWaker,
wake: unsafe fn(*const ()),
wake_by_ref: unsafe fn(*const ()),
drop: unsafe fn(*const ()),
}
三、进一步展开:“type erasure” 和“_”
感谢https://www.zhihu.com/question/626511636/answer/3257068663中相关高手的指点,关于*const(),和unsafe fn (*const())有了进一步的认识。
1、*const()是一个指向一个没有类型信息的常量数据的指针,可能是某个数据类型,也可能是某个函数;
2、fn (*const())是一个指向以没有类型信息的常量数据指针为参数的函数。
3、还有,特别是“_”的作用,能够起到“type erasure”的作用。
rust type erasure
Rust语言中的类型擦除是指在运行时将特定类型的信息丢弃。它常常用于动态分配内存,例如在Box或Vec中,其中Trait是一个关联类型。
擦除类型的主要目的是使用泛型代码时避免代码复制,并以此获得更好的性能。此外,它还可以简化代码,因为不需要为每个类型定义一个独立的函数版本。
不幸的是,类型擦除会带来一些限制,例如无法在运行时获取特定类型的信息,因为该信息已被擦除。因此,在使用类型擦除时必须谨慎,以确保代码仍具有正确性和可读性。
struct Foo(String);
impl Foo{
//无返回参数
fn call(&self){
println!("rust, {:}",self.0);
}
//返回参数类型
fn get_id(&self) ->usize{
println!("string len :{:?}",self.0.len());
self.0.len()
}
// 加入其它参数
fn islimited(&self,limit:usize)->bool{
if self.0.len() >limit{
println!("注意!已经超限:{:?}",self.0.len());
return true
}else{
println!("安全!没有超限:{:?}",self.0.len());
return false
}
}
}
struct Bar{
data: *const(),
fn_void: unsafe fn(*const()),
fn_self: unsafe fn(*const ())->usize,
fn_para: unsafe fn (*const(),usize) ->bool,
}
fn main(){
let foo = Foo(String::from("HELLO WORLD!"));
let ptr :*const() = &foo as *const Foo as _; //“_”在这里起到关键的作用
let fn_void :unsafe fn (*const ()) = unsafe{
std::mem::transmute(Foo::call as fn(&Foo))
};
let fn_self : unsafe fn(*const ()) ->usize = unsafe{
std::mem::transmute(Foo::get_id as fn(&Foo)->usize)
};
// 加入参数
let fn_para : unsafe fn(*const (),usize)->bool = unsafe{
std::mem::transmute(Foo::islimited as fn(&Foo,usize)->bool)
};
let bar = Bar{
data : ptr,
fn_void : fn_void,
fn_self :fn_self,
fn_para : fn_para,
};
unsafe{(bar.fn_void)(bar.data)};
unsafe{(bar.fn_self)(bar.data)};
unsafe{(bar.fn_para)(bar.data,100 as usize)};//
}
输出结果:
rust, HELLO WORLD!
string len :12
安全!没有超限:12
4、关于fn(*const ())之类的初始化:闭包来简化
pub struct Waker {
wake: unsafe fn(*const ()),
load: unsafe fn(*const ()) -> String,
}
fn main(){
let wake = |_| {};
let load = |_| {"".to_string()};
let w = Waker{wake,load };
println!("hello world");
}