rust 如何使用'borrow::Borrow< ...>'来接受任何“borrows”作为特征的东西?

0g0grzrc  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(112)

documentation for Rust's core::convert::AsRef trait表示:
... Borrow对任何T都有一个blanket impl,并且可以用来接受引用或值。
然后继续执行link to the core::borrow::Borrow trait
实际上,它可以用来编写泛型代码,通过传值或引用来接受参数--它代表了 * 的概念,而不是可以作为&T * 来借用的任何东西,由于T可以作为&T来借用,所以这个简单的例子可以完美地工作:

fn report_by_either<T: Borrow<i32>>(either: T) {
    let x: i32 = *either.borrow();
    println!("x = {}", x);
}
⋮

report_by_either(5); // x = 5
report_by_either(&6); // x = 6

如果希望在更复杂的场景中使用Borrow<…>,具体来说:在带有泛型约束的泛型代码中,我们如何能够额外地表达T实现一个特征的约束,而不是表示 * 任何借用为&T * 的东西的概念?
最近,当我试图解决Rust's ranges do not all implement Copy的问题时,我想到了一个非常简单的例子。
考虑这个函数,它接受任何可以提供RangeBounds<i32>的函数:

fn report_by_value<R: RangeBounds<i32> + Debug>(value: R) {
    println!("range-bounds: `{:?}`", value);
}

这导致呼叫者的不满意的不一致体验:
1.如果它们传递了实现Copy的类型(例如RangeToRangeToInclusive...)的 * 一些 * 范围,它们就不会有问题。

let range = ..100;
report_by_value(range);
report_by_value(range);
report_by_value(range);
  • 但是,对于其他范围(例如RangeRangeFrom...),他们最好调用clone(),否则该范围的所有权将被窃取:
let range_from = 1..;
report_by_value(range_from.clone());
report_by_value(range_from.clone());

report_by_value(range_from);
report_by_value(range_from); // use of moved value: `range_from`

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

fn report_by_reference<R: RangeBounds<i32> + Debug>(reference: &R) {
    println!("range-bounds: `{:?}`", reference);
}

但这 * 也 * 会导致调用站点的代码很笨拙:

report_by_reference(&(4..));
report_by_reference(&(..5));
report_by_reference(&(6..7));

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

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

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

  • 以下两个调用都会产生错误:“* 无法推断在函数report_by_either * 上声明的类型参数R的类型":
report_by_either(5..); // cannot infer type of the type parameter `R` declared on the function `report_by_either`
report_by_either(&(6..)); // cannot infer type of the type parameter `R` declared on the function `report_by_either`
  • 这些 turbo-fish-枯萎线工作:
report_by_either::<RangeFrom<i32>, _>(7..);
report_by_either::<RangeFrom<i32>, _>(&(8..));

在这个特定的场景中,我不希望我的用户必须理解甚至意识到why the range-types don't implement Copyinconsistencies that mean that not all of them don't的原因。我希望我的用户能够传递给我任何给出RangeBounds<…>的东西。也就是说,在这个例子中,在我的API中只接受范围边界并不是一个灾难性的妥协-report_by_reference(&(6..7))很笨拙,但可以忍受。
然而,更一般地说,我认为“任何作为一种特质而借用的东西”的概念肯定是一种普遍的需要。
我应该如何实现它?

hgqdbh6s

hgqdbh6s1#

你在这里运气不好在6..7和你的&i32之间有两个间接特性,编译器不能推导出中间类型。例如,我可以创建一个类型,它满足 could 是中间类型的约束:


# [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);
}

如果允许省略显式类型参数,编译器应该使用哪种实现?Range<i32>?还是我的Gobbledygook类型?为什么?编译器不会采取这种或那种方式。
所以如果你有T: Trait<U>, U: Borrow<V>T: Borrow<U>, U: Trait<V>,你总是需要指定中间类型。尽管如果Trait使用了一个关联类型而不是泛型类型参数,那么在前一种情况下,中间类型可以被明确地推导出来。

相关问题