gpt4 book ai didi

rust - 接受 `impl Borrow` 的参数来抽象 T 的引用和值是惯用的 rust 吗?

转载 作者:行者123 更新时间:2023-12-04 11:38:49 26 4
gpt4 key购买 nike

关闭。这个问题是opinion-based .它目前不接受答案。












想改善这个问题吗?更新问题,以便可以通过 editing this post 用事实和引文回答问题.

9 天前关闭。




Improve this question




我发现自己编写的函数接受参数为 Borrow<T>以便它透明地接受值和引用。
例子:

use std::borrow::Borrow;

#[derive(Debug, Copy)]
struct Point {
pub x: i32,
pub y: i32,
}

pub fn manhattan<T, U>(p1: T, p2: U) -> i32
where
T: Borrow<Point>,
U: Borrow<Point>,
{
let p1 = p1.borrow();
let p2 = p2.borrow();
(p1.x - p2.x + p1.y - p2.y).abs()
}
这对于实现 std:ops 很有用喜欢 Add ,否则将需要大量重复以透明地支持引用。
这是惯用语吗?有缺点吗?

最佳答案

我认为这个问题有两个部分。
1.是Borrow trait 在 Rust 中抽象所有权的惯用方式?
.如果您打算编写一个接受 Foo 的函数或 &Foo , F: Borrow<Foo>是使用的权利。 AsRef另一方面,通常只为类似引用的东西实现,而不是为拥有的值实现。
2. 在 Rust 中抽象所有权是惯用的吗?
有时 .这是一个有趣的问题,因为像 manhattan 这样的函数之间存在微妙但重要的区别。以及如何Borrow是惯用的。
在 Rust 中,函数是需要拥有它的参数还是仅仅借用它们是函数接口(interface)的重要组成部分。通常,Rustaceans 不介意写作 &在函数调用中,因为它是关于被调用函数的相关语义事实的句法标记。一个可以接受 Point 的函数或 &Point不比只能接受的更普遍有用&Point : 如果你有 Point ,您所要做的就是借用它。所以使用更简单的签名是惯用的,它最准确地记录了函数真正需要的类型:&Point .
可是等等!这些接受论点的方式之间还存在其他差异。一个区别是调用开销:a &Point通常会在单个指针大小的寄存器中传递,而 Point可以在多个寄存器或堆栈上传递,具体取决于 ABI。另一个区别是代码大小:<T: Borrow<Point>> 的每个唯一实例化表示函数的单态化,这会使二进制膨胀。第三个区别是降序:if Point有析构函数,一个接受 T: Borrow<Point> 的函数将调用 Point::drop在内部,而接受 &Point 的函数将对象留在原地供调用者处理。这是好是坏取决于上下文;但是,对于性能而言,它通常无关紧要(如果您假设 Point 无论如何最终都会被删除)。
一个接受 T: Borrow<Point> 的函数表明它正在用 T 做一些事情在内部,只有 &Point可能是次优的。 Drop order 可能是这样做的最佳原因(我写了更多关于这个 in this answer 的文章,尽管我用作示例的 puts 函数并不是特别强大的函数)。
manhattan的情况下降序无关紧要,因为 PointCopy (简单复制的类型可能没有滴胶)。所以接受 Point 没有性能优势以及 &Point (虽然单个函数不太可能以某种方式产生太大差异,但如果泛型被普遍使用,代码大小的成本很可能是一个缺点)。
避免不必要地使用泛型还有一个原因:它们会干扰类型推断,并且会降低编译器的错误消息和建议的质量。例如,假设 Point仅实现 Clone (不是 Copy )并且你写了 manhattan(p, q)然后使用 p稍后在同一函数中再次使用。编译器会警告你 p移入函数后使用,建议添加.clone() .其实更好的办法是借p ,如果 manhattan接受引用编译器将强制您这样做。
事实Point很小(因此将其用作函数参数的开销可能很小)和 Copy (所以不用担心滴胶)提出另一个问题:应该manhattan只需接受 Point并且根本不使用引用?这是一个基于意见的问题,实际上归结为哪个更适合您的心理模型。要么接受 &Point ,并使用 &当调用者拥有自有值时,或接受 Point ,并使用 *当来电者有引用时 - 没有硬性规定。Borrow的正确用法是什么? , 然后?
上面的论点强烈依赖于引用很容易在任何地方使用的事实,因此您也可以在调用者中具体地将它们作为抽象地在泛型函数中。有一次情况并非如此,即借用或拥有的类型没有直接传递给函数,而是包装在另一个通用数据结构中。考虑对 Point 的切片进行排序- 类似事物与 (0, 0) 的距离:

fn sort_by_radius<T: Borrow<Point>>(points: &mut [T]) {
points.sort_by_key(|p| {
let Point { x, y } = p.borrow();
x * x + y * y
});
}
在这种情况下,绝对不是带有 &mut [Point] 的调用者的情况。可以简单地借用它来获得 &mut [&Point] .然而我们想要 sort_by_radius能够接受两种切片(无需编写两个函数)所以 Borrow<Point>来救援。 sort_by_radius的区别和您的版本 manhattanT不是直接传递给要立即借用的函数,而是 sort_by_radius 类型的一部分需要像对待 Point为了执行最终与借用无关的任务(对切片进行排序)。

关于rust - 接受 `impl Borrow<T>` 的参数来抽象 T 的引用和值是惯用的 rust 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69307184/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com