【问题标题】:How can I generalise a function that accepts an `&[T]` so that I can still invoke it with a bytestring literal?如何概括一个接受 `&[T]` 的函数,以便我仍然可以使用字节串文字来调用它?
【发布时间】:2017-01-30 03:39:53
【问题描述】:

我从一个有点像这样 (playground) 的函数开始:

fn find <T: Copy> (src: &[T], index: usize) -> T {
    // more complex algorithm, involving src goes here
    src[index]
}

pub fn main() {
    let x = b"abc";
    assert_eq!(b'b', find(x, 1));    
}

我想概括它,以便我可以为src 使用任何合适的类型。我想出的最好的就是这个(playground):

trait RandomAccess<T> {
    fn get_at(&self, index: usize) -> T;
}

impl <T: Copy> RandomAccess<T> for [T] {
    fn get_at(&self, index: usize) -> T {
        self[index]
    }
}

fn find <T: Copy, S: ?Sized + RandomAccess<T>> (src: &S, index: usize) -> T {
    // more complex algorithm, involving src goes here
    src.get_at(index)
}

pub fn main() {
    let x = b"xyz";
    assert_eq!(b'y', find(&x[..], 1));
}

但是,我现在不能只调用find(x, 1),我必须创建一个切片:find(&amp;x[..], 1)

有没有办法使这个通用化,但仍然能够像原始示例一样调用find

【问题讨论】:

  • 似乎 rust 编译器在第一个示例中在幕后做了一些诡计,并允许将&amp;[u8; 3] 强制转换为&amp;[u8],但在第二个示例中不能这样做,因为它不能确定这是意图。基本上是这个问题吗?
  • 我尝试对 Index 和其他一些东西进行抽象,但我不相信你需要引入这样的抽象——它们往往会使事情复杂化而没有附加价值。其他src 类型find() 可以适用于哪些?
  • 诡计在于:如果目标类型已知,则强制触发。因为find 使用src 的类型参数,所以不是。

标签: rust slice traits


【解决方案1】:

由于 rust 编译器 (https://github.com/rust-lang/rust/issues/29504) 的限制,您的第二个示例目前无法运行。但是,有几种方法可以解决这个问题。

最简单的方法是为所有C: AsRef&lt;[T]&gt; 实现RandomAccess&lt;T&gt;。这样,它将与[T; n]&amp;[T]Vec&lt;T&gt; 等一起使用:

trait RandomAccess<T> {
    fn get_at(&self, index: usize) -> T;
}

impl<T: Copy, C: AsRef<[T]>> RandomAccess<T> for C {
    fn get_at(&self, index: usize) -> T {
        self.as_ref()[index]
    }
}

fn find<T: Copy, C: RandomAccess<T>>(src: C, index: usize) -> T {
    src.get_at(index)
}

不幸的是,如果这样做,您将无法添加任何其他 RandomAccess 实现,因此您不妨更改 find 以获取一些满足 AsRef&lt;[T]&gt; 的集合:

fn find<T: Copy, C: AsRef<[T]>> (src: C, index: usize) -> T {
    src.get_at(index)
}

或者,如果您需要能够支持 不能 借用为 [T] 的集合,您可以在某个范围内为所有 n 实现 RandomAccess&lt;T&gt; for [T; n]使用宏:

trait RandomAccess<T> {
    fn get_at(&self, index: usize) -> T;
}

impl<T: Copy> RandomAccess<T> for [T] {
    fn get_at(&self, index: usize) -> T {
        self[index]
    }
}

macro_rules! impl_random_access {
    ($($n:expr,)*) => {
        $(
            impl <T: Copy> RandomAccess<T> for [T; $n] {
                fn get_at(&self, index: usize) -> T {
                    self[index]
                }
            }
        )*
    }
}

impl_random_access! {
    01,02,03,04,05,06,07,08,
    09,10,11,12,13,14,15,16,
    17,18,19,20,21,22,23,24,
    25,26,27,28,29,30,31,32,
}



fn find<T: Copy, S: ?Sized + RandomAccess<T>>(src: &S, index: usize) -> T {
    src.get_at(index)
}

当我们最终获得类型级常量(目前在愿望清单上)时,您应该能够为所有 [T; n] 实现 RandomAccess&lt;T&gt;。但现在,您需要使用宏。

【讨论】:

  • AsRef 具有相同的限制,只能实现到 [T; 32]
  • 您能解释一下简单版本 (fn find &lt;T:&gt; (src: &amp;[T])) 的工作机制吗?将&amp;[T; n]强制转换为&amp;[T]是编译器的特例吗?
  • 这是一个错误:github.com/rust-lang/rust/issues/29504。我已经更新了答案。
  • @PeterHall 是的,它是内置的
猜你喜欢
  • 2022-09-24
  • 1970-01-01
  • 1970-01-01
  • 2019-05-31
  • 1970-01-01
  • 1970-01-01
  • 2017-05-20
  • 2012-03-13
  • 2019-10-02
相关资源
最近更新 更多