如何使用“borrow::Borrow<…>”接受任何“借用”特征的内容?

Xha*_*lie 5 generics rust borrow-checker

Rust 特征文档core::convert::AsRef

\n
\n

\xe2\x80\xa6Borrow有一个针对任何T,可用于接受引用或值。

\n
\n

然后它继续链接到core::borrow::Borrow特征

\n

事实上,它可以用来编写可以通过值或通过引用接受参数的通用代码 \xe2\x80\x93 它表示任何可以借用为&Tand 的概念,因为T可以借用为&T,这个简单的例子可以工作完美:

\n
fn report_by_either<T: Borrow<i32>>(either: T) {\n    let x: i32 = *either.borrow();\n    println!("x = {}", x);\n}\n\xe2\x8b\xae\n\nreport_by_either(5); // x = 5\nreport_by_either(&6); // x = 6\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果希望具体地在更复杂的场景中使用Borrow<\xe2\x80\xa6>\xe2\x80\x94 该怎么办:在具有通用约束的通用代码中。不是表示任何借用为 的&T概念,而是如何另外表达这样的约束:T,而是如何另外表达实现特征的

\n

最近,当我试图解决Rust \ 的范围并不全部实现的事实时,我想到了一个非常简单的例子Copy这一事实时,我想到了一个非常简单的例子。

\n

考虑这个函数,它接受任何可以提供的东西RangeBounds<i32>

\n
fn report_by_value<R: RangeBounds<i32> + Debug>(value: R) {\n    println!("range-bounds: `{:?}`", value);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这会给调用者带来令人不满意的不一致体验:

\n
    \n
  1. 如果它们通过了实现的类型的某些Copy范围(例如RangeToRangeToInclusive , \xe2\x80\xa6),它们就会很好。

    \n
    let range = ..100;\nreport_by_value(range);\nreport_by_value(range);\nreport_by_value(range);\n
    Run Code Online (Sandbox Code Playgroud)\n
  2. \n
  3. 但是,对于其他范围(例如Range, RangeFrom, \xe2\x80\xa6),他们最好调用clone(),否则范围的所有权会被盗:

    \n
    let range_from = 1..;\nreport_by_value(range_from.clone());\nreport_by_value(range_from.clone());\n\nreport_by_value(range_from);\nreport_by_value(range_from); // use of moved value: `range_from`\n
    Run Code Online (Sandbox Code Playgroud)\n\n
  4. \n
\n

避免这种不一致的一种方法是通过引用接受范围:

\n
fn report_by_reference<R: RangeBounds<i32> + Debug>(reference: &R) {\n    println!("range-bounds: `{:?}`", reference);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但这导致调用站点的代码变得笨拙:

\n
report_by_reference(&(4..));\nreport_by_reference(&(..5));\nreport_by_reference(&(6..7));\n
Run Code Online (Sandbox Code Playgroud)\n

看起来显而易见的解决方案是使用borrow::Borrow

\n
fn report_by_either<R: RangeBounds<i32> + Debug, T: Borrow<R>>(either: T) {\n    println!("range-bounds: `{:?}`", either.borrow());\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但不幸的是,这引发了这个问题,因为泛型类型的类型推断对此不起作用。

\n
    \n
  • 以下两个调用都会产生错误:“无法推断函数上声明的类型参数的类型Rreport_by_either”:

    \n
    report_by_either(5..); // cannot infer type of the type parameter `R` declared on the function `report_by_either`\nreport_by_either(&(6..)); // cannot infer type of the type parameter `R` declared on the function `report_by_either`\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
  • 这些涡轮鱼枯萎线确实有效:

    \n
    report_by_either::<RangeFrom<i32>, _>(7..);\nreport_by_either::<RangeFrom<i32>, _>(&(8..));\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
  • 操场

    \n
  • \n
\n

在这种特殊情况下,我不希望我的用户必须理解甚至意识到范围类型未实现的Copy原因或意味着并非所有范围类型都未实现的不一致。我希望我的用户能够将任何提供给我的东西传递给我RangeBounds<\xe2\x80\xa6>。也就是说,在本例中,通过引用仅接受范围边界并不是我的 API 中的灾难性妥协 \xe2\x80\x93report_by_reference(&(6..7))很笨重但可以忍受。

\n

然而,更一般地说,我认为任何借用为特征的概念肯定是一种普遍的愿望。

\n

我应该如何实现它?

\n

kmd*_*eko 2

你在这里很不走运;您有两个间接特征,6..7并且&i32编译器无法推断出中间类型。例如,我可以创建一个满足中间类型约束的类型

#[derive(Debug)]
struct Gobbledygook;

impl RangeBounds<i32> for Gobbledygook {
    fn start_bound(&self) -> Bound<&i32> { todo!() }
    fn end_bound(&self) -> Bound<&i32> { todo!() }
}

impl Borrow<Gobbledygook> for Range<i32> {
    fn borrow(&self) -> &Gobbledygook { todo!() }
}

fn report_by_either<R: RangeBounds<i32> + Debug, T: Borrow<R>>(either: T) {
    println!("range-bounds: `{:?}`", either.borrow());
}

fn main() {
    report_by_either::<Gobbledygook, _>(0..7);
}
Run Code Online (Sandbox Code Playgroud)

如果允许省略显式类型参数,编译器应该使用哪种实现?Range<i32>?还是我的Gobbledygook类型?为什么?编译器不会采用其中一种方式。

因此,如果您有T: Trait<U>, U: Borrow<V>T: Borrow<U>, U: Trait<V>总是必须指定中间类型。尽管如果Trait使用关联类型而不是泛型类型参数,则可以在前一种情况下明确推导中间类型。