【问题标题】:Do I need any of the *variantType markers?我需要任何 *variantType 标记吗?
【发布时间】:2014-08-18 14:10:37
【问题描述】:

我正在尝试以来自 Rust 的类型安全方式与类型不安全的 C 库进行交互。 C 接口处理一些void 指针。具体来说,有一个write 和一个read 函数将这样一个void 指针作为参数并做一些显而易见的事情(将给定的数据写入到其他地方或从那个地方读取到给定的缓冲区)。这是什么类型的数据,早先 py 已同意将某个整数值添加到 open 函数

1 = float
2 = int32_t
4 = int16_t
etc...

我用一个像这样的参数化 Rust 结构表示一个打开的输出流(用户可以在其中调用 write on):

pub struct OutputStream<T> {
    c_stream: *mut ()
}

impl<T: ValueType> for OutputStream<T> {
    pub fn write(&mut self, data: &[T]) -> Result<(),SomeErrorType> {
        unsafe { ... }
    }
}

ValueType 是我为所有支持的值类型编写的特征,其他人无法实现它(私有基本特征技巧)。因此,显然 T 在结构中的任何地方都没有提及。这似乎是ContravariantType 标记的情况。但我不太了解这些类型标记。访问关于方差的维基百科页面没有帮助。我只是看不到方差与如何应用于 Rust 的类型参数之间的联系。而且我真的不知道这种标记实际上会阻止什么。

所以我的问题是:我的OutputStream 是否需要这些标记之一?如果是这样,是哪一个?为什么?我究竟会通过使用它来防止什么? InputStream 也是如此,它提供了 read 方法而不是 write 方法。

【问题讨论】:

    标签: rust ffi type-safety


    【解决方案1】:

    一般来说,方差决定了参数化类型之间关于其参数的子类型关系:

    Covariance:      T <: U    =>    F[T] <: F[U]
    Contravariance:  T <: U    =>    F[U] <: F[T]
    Invariance:      T <: U    =>    neither of the above
    Bivariance:      T <: U    =>    both of the above
    

    你的类型自然逆变的:它的方法只使用类型为T的值,但不产生它们;这就是所谓的消费者类型。然而,Rust 中的子类型非常有限。据我所知,唯一允许任何类型的子类型关系的类型是引用(例如,您可以将&amp;'static str 传递给&amp;'a str 变量,因为'static 生命周期大于或等于任何其他生命周期,所以@ 987654326@ 是任何'a&amp;'a str 的子类型。

    所以,如果我理解正确,您确实需要方差注释。如果您的T 参数可以作为参考,请使用ContravariantType,这样您就可以这样做:

    fn push_something_to(os: OutputStream<&'static str>) { ... }
    
    let s: OutputStream<&'a str> = ...;  // and 'a is less than 'static
    push_something_to(s);   // this is safe to do because &'static str is valid &'a str
    

    但不能这样做:

    let s: OutputStream<int> = ...;
    push_something_to(s);   // oops, push_something_to expects stream of &'static str
    

    InvariantType 两者都被禁止。


    但是,我这边似乎有一些深刻的误解,因为文档中关于方差标记的代码和我自己的代码都不适用于我当前的 Rust:

    use std::ptr;
    use std::mem;
    
    struct S<T> { x: *const () }
    
    fn get<T>(s: &S<T>, v: T) {
       unsafe {
           let x: fn(T) = mem::transmute(s.x);
           x(v)
       }
    }
    
    fn main() {
        let s: S<int> = S { x: ptr::null() };
        get::<Box<int>>(&s, box 1);
    }
    

    根据文档,由于默认情况下参数化类型是双变量的,这应该可以编译,但事实并非如此:它就像参数是不变的一样。

    这是我自己的例子:

    #![allow(dead_code)]
    
    type F<T> = fn(T);
    
    fn test_1<'a>(f: F<&'a str>) -> F<&'static str> {
        f
    }
    
    struct S<T> {
        _m: std::kinds::marker::ContravariantType<T>
    }
    
    fn test_2<'a>(s: S<&'a str>) -> S<&'static str> {
        s
    }
    
    fn main() {}
    

    据我了解,这个程序应该可以编译,但它没有:

    <anon>:9:5: 9:6 error: mismatched types: expected `S<&'static str>` but found `S<&'a str>` (lifetime mismatch)
    <anon>:9     s
                 ^
    <anon>:8:47: 10:2 note: the lifetime 'a as defined on the block at 8:46...
    <anon>:8 fn test<'a>(s: S<&'a str>) -> S<&'static str> {
    <anon>:9     s
    <anon>:10 }
    note: ...does not necessarily outlive the static lifetime
    

    如果我删除 Stest_2,它编译得很好,这表明函数类型在其参数类型方面确实是逆变的。

    我不知道发生了什么事,看起来值得另一个问题。

    【讨论】:

    • 谢谢!如果现在子类型真的只是在具有不同生命周期参数的类型之间受到限制(我想这可能会随着结构继承而改变)我不需要担心这个,因为在我的情况下我只处理像i16i32 这样的事情,f32。但我认为我仍然需要暂时了解方差的问题。
    • 如果文件中关于这些标记的声明属实,那么您确实需要其中一个标记;我想,InvariantType 是最合适的。但鉴于这些实验结果,坦率地说我不知道​​。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-28
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 2013-02-20
    • 2020-07-14
    • 1970-01-01
    相关资源
    最近更新 更多