Rust元组和元组引用的参考文献

3

元组引用和引用元组作为类型之间的关系是什么?
为什么第一个可以工作,而第二个不行?

let a = 1;
let b = 2;
// This works, c: &i32, d:&i32
let (c, d) = &(a, b);

type TupleOfRef<'a> = (&'a i32, &'a i32);
let e = (a, b);
// This doesn't
let f: TupleOfRef = &e;

为了更清晰地阐述我的问题,它更多关于类型(&'a A, &'a B)(A, B)的关系。

虽然元组的内存布局不能保证,但很明显不能通过克隆来从&A&B制作出&(A, B),因为没有一个内存地址持有AB

然而,制作(&A, &B)使得(A, B)有些意义,因为我们不仅有元组的地址(即&(A, B)),还有它的元素的地址(即&A&B,正如@etchesketch所提到的)。这在上面例子的第一种情况中似乎有效,但第二种情况不是。

实际上第二个是我想要的。有没有一种通用的方法可以将拥有的(A,B,..)以(&A,&B,..)的方式输出?或者有没有任何好的方法来表达这些“匹配性”在特质限定中?
下一个问题:元组的引用和元组的引用的类型级操作

1
这是因为在第一个情况下您没有指定类型,所以编译器足够聪明以推断类型。如果您提到类型,它将会失败,就像后面的情况一样。 - vikram2784
2
我认为这也与匹配人体工程学有关。 - cadolphs
3个回答

4
简单的回答是 &(i32, i32)(&i32, &i32) 不是同一种类型,但这可能不是你想要的详细信息级别。

如果你从内存布局的角度来考虑,或许更容易理解。cheats.rs有一些很好的插图,我会在这里不要脸地借鉴:

Tuple memory layout

重要的是,无论您在元组中存储什么,元素在内存中始终是连续的*请参见注释。对于拥有类型和引用,这是正确的,只是当元素是引用时,它指向的内存不一定是连续的。

这就是为什么在&(T, U)(&T, &U)之间进行转换是非常棘手的原因。在前一种情况下,您有一个TU“捆绑包”(意味着它们在物理上在一起),并且引用指向“捆绑包”。在后一种情况下,您拥有&T&U的所有权“捆绑包”(意味着引用在物理上在一起,但它们可以分别指向任何地方)。


修改:

是的,你可以从&(T, U)创建一个(&T, &U),因为你当然可以从&(T, U)获取&T&Utuples crate提供了TupleAsRef特质,它适用于最多32个元素的元组:

use tuples::TupleAsRef;

let t = (69, 420);
assert_eq!(t.as_ref(), (&69, &420));

4
请注意,结构体(和元组)可能不会存储连续的数据,因为可能存在填充,但除此之外,您的解释是正确的。 - Chayim Friedman
@ChayimFriedman 感谢您的纠正。我忽略了那个。 - cyqsimon
据我所知,元组的布局顺序也不能保证。因此,布局可能是C,然后是A,然后是B,例如。 - cdhowie
@cyqsimon 感谢您的友善解释。我知道它们不是同一类型。我已经更清楚地表达了我的观点,请给予指导和启发。 - lighthouse
鉴于上面的图像,很可能物理布局将是BCA,因为(与结构体一样)编译器将按照从大到小的顺序重新排列字段,以限制填充。 - Masklinn

2

&(a, b) 可以解构成两个变量,它们都是 &i32 类型,但这并不等同于拥有一个类型为 (&i32, &i32) 的元组。

你可以通过使用 let f: TupleOfRef = ( &e.0, &e.1 ); 使第二个示例正常工作。


谢谢您的回答。实际上,这更接近我想要的东西。但是,在一般情况下,对于n元组的第一个示例,有没有不索引元素的好方法来完成它? - lighthouse
@lighthouse 我不知道你的n元问题的答案,所以我四处查找,看起来似乎没有直接的解决方案。你的元组项都是相同类型吗?为什么不使用一个向量呢? - etchesketch
不,它们是异构的。因为这涉及到类型级编程,不能依赖于堆分配和运行时信息。 - lighthouse

1

如果你经常这样做,可以通过trait使其更加轻松:

trait TupleOfRefs<'a> {
    type Output: 'a;
    fn as_tuple_of_refs (&'a self) -> Self::Output;
}

impl<'a, A: 'a> TupleOfRefs<'a> for (A,) {
    type Output = (&'a A,);
    fn as_tuple_of_refs (&'a self) -> Self::Output {
        (&self.0,)
    }
}

impl<'a, A: 'a, B: 'a> TupleOfRefs<'a> for (A, B) {
    type Output = (&'a A, &'a B);
    fn as_tuple_of_refs (&'a self) -> Self::Output {
        (&self.0, &self.1)
    }
}

fn main() {
    let a = 1;
    let b = 2;
    
    let t = (a, b);
    let r = t.as_tuple_of_refs();
    
    println!("{r:?}");
}

游乐场

缺点是你需要为所有可能的元组大小先实现该特性(虽然可以通过宏简化):

macro_rules! make_tuple_of_refs {
    ($($t:ident $i:tt),*) => {
        impl <'a, $($t: 'a),*> TupleOfRefs<'a> for ($($t,)*) {
            type Output = ($(&'a $t,)*);
            fn as_tuple_of_refs (&'a self) -> Self::Output {
                ($(&self.$i,)*)
            }
        }
    }
}

trait TupleOfRefs<'a> {
    type Output: 'a;
    fn as_tuple_of_refs (&'a self) -> Self::Output;
}
make_tuple_of_refs!(A 0);
make_tuple_of_refs!(A 0, B 1);

游乐场


1
非常感谢。这是我需要的最接近的东西。我已经测试了您的方法,它完美地工作,除了非元组类型的情况。实际上,这是用于类型级别的组合操作。例如,(&A) -> A x (&B) -> B = ((&A, &B)) -> (A, B)。我定义了一个类似函数的特质,将操作作为成员函数与相关联的类型的参数类型。普通的引用不起作用,因为Compose<OP::<T=A>, OP::<T=B>>的结果将是(&(A, B)) -> (A, B),而不是(&A, &B) -> (A, B)。您有什么建议吗? - lighthouse
我已经发布了一个单独的问题。https://stackoverflow.com/questions/75895144/tuple-of-reference-and-reference-of-tuple-for-type-level-operation - lighthouse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接