我们在阅读底层的代码过程中,经常会看*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");
    
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐