在Rust中,有没有可能在编译时知道一个变量是否是引用?

vvppvyoh  于 2022-11-12  发布在  其他
关注(0)|答案(2)|浏览(130)

给定以下代码:

fn main() {
    let a: i32 = 1;
    let b: &i32 = &a;
    let c: String = "hello world".to_string();
    let d: &str = &c[..];
}

我知道了

  • a是一个正常的变量,
  • b是保存指针的引用
  • c是一个引用,尽管它是一个智能引用,因为除了保存指针之外,它还包含其他数据,如长度、容量等。
  • d是一个引用,不是一个聪明的引用,但我看到的通常被称为胖指针。它包含指针和简单的额外信息,如指向的数据的长度。

好了,现在我的问题是,有没有一种方法可以在编译时检查这些变量或类型,以知道它们是哪种类型的引用?
我知道有几个函数可以用来内省类型。比如std::mem::size_ofstd::mem::align_of。我想知道有没有可以用来推断某个东西是什么类型的引用的函数?

chhkpiq4

chhkpiq41#

看起来,你并不完全理解rust-land中的引用是什么,你的描述表明你对C++中的引用有着非常相似的理解,但是在rust-land中,它是完全不同的。
在rust中,引用是类型T&T&mut T是三种不同的类型。它们类似于指针,但有额外的保证,其中最重要的是:

  • 它们从不为空
  • 它们不会比它们所指向的数据更持久
  • 对于给定对象,一次只能有一个可变引用或任意数量的共享引用

你也可以有对其他引用的引用(就像在C中你可以有一个指针对一个指针),所以T&T&&T&&&T等是不同的类型。
现在当我们说某个东西是一个引用时,它意味着它的类型是某种类型的引用。

fn main() {
    let a: i32 = 1;
    let b: &i32 = &a;
    let c: String = "hello world".to_string();
    let d: &str = &c[..];
}
  • a具有类型i32(带符号的整数)
  • b的类型为&i32,因此它是对数字的引用
  • c有一个String类型,它不是一个引用。当然,它内部包含一个指向堆分配缓冲区的指针,但是String类型只是String,而不是对任何其他类型的引用
  • d的类型是&str,它是一个引用。更具体地说,它是一个字符串slice的引用。正如您所指出的,它是一个“胖”指针,因为它不仅包含了切片开始的地址,而且还包含了切片的长度。

所以最后解决你的问题。在rust中只有两种引用,共享的&T和独占的由于所有类型必须在编译时知道,所以没有任何运行时内省或反射函数来告诉你什么类型的引用是某个类型。rust中没有“智能引用”这样的东西,但是有一种叫做smart pointers的东西,尽管它是非常不同的东西。

kmbjn2e3

kmbjn2e32#

如果您只想知道引用指向什么,那么您可以使用ToOwned特征(例如:<str as ToOwned>::Owned的计算结果为String)。但是,在我看来,您可能需要的是Cow<'a, T>。它们的名称代表Clone On Write。在它们的核心,它们只是引用或拥有值的枚举。

pub enum Cow<'a, B> 
where
    B: 'a + ToOwned + ?Sized, 
{
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}

它们对于字符串和切片的操作非常有用,您可能需要修改输入,但如果输入已经有效,则不希望复制所有数据。主要优点是它们可以被视为拥有值,但给予您根据性能灵活地在两者之间切换。

use std::borrow::Cow;

pub fn remove_fives(slice: &[i32]) -> Cow<[i32]> {
    if slice.iter().any(|x| *x == 5) {
        // We found some 5s to remove so we need to clone the input and remove them
        // from the result. This creates a new Vec<i32> we can modify.
        let mut owned_values = slice.to_owned();
        owned_values.retain(|x| *x != 5);

        Cow::Owned(owned_values)
    } else {
        // No fives were found so no we don't need to allocate anything or do extra work
        Cow::Borrowed(slice)
    }
}

作为一个有趣的附带点,如果我们只理解问题的标题,那么编写一个在编译时解析为一个值的简单is_ref<T>()函数并不太难。它使用了类似于impls crate的特征。这可能很有趣,但可能并不是那么有帮助。

pub const fn is_ref<T: ?Sized>() -> bool {
    trait IsNotRef {
        const IS_REF: bool = false;
    }

    impl<A: ?Sized> IsNotRef for A {}

    struct Wrapper<A: ?Sized>(::std::marker::PhantomData<A>);

    impl<'a, A: ?Sized> Wrapper<&'a A> {
        const IS_REF: bool = true;
    }

    impl<'a, A: ?Sized> Wrapper<&'a mut A> {
        const IS_REF: bool = true;
    }

    <Wrapper<T>>::IS_REF
}

相关问题