"as_ref"Rust 实现惯用通用的方式是吗?



在我的理解中,锈是一个"有一个";而不是一个"是一个"语言(复合甚于继承)。这使得Liskov替换稍微复杂一些,但并不是不可能使用Traits。虽然我可以使用LSP,但它似乎不是rust中实现类型强制转换的惯用方法。我不知道怎么操作。

最小的例子假设我有两个结构体

struct Real(i32);
struct Complex(Real, Real);

和一个函数project,它取一个Complex并返回一个输入的投影。

#[derive(Clone, Copy)]
struct Real(i32);
struct Complex(Real, Real);
// we pass by reference because we need to be blazingly fast
fn project(c : &Complex) -> Complex {
Complex(c.0, Real(0))
}
fn main() {
let a = Complex(Real(1), Real(2));
let x = project(&a);
println!("{} + {}i", x.0.0, x.1.0)
}

为了简单起见,请假设我们的情况是,我们受益于通过引用传递Real,project不应该被复制为RealComplex的特性的多个实现。假设我们希望在Real上也经常使用project

使project有点通用

我的面向对象本能促使我为RealComplex创建一些超类型,假设是AsReal特征

#[derive(Clone, Copy)]
struct Real(i32);
struct Complex(Real, Real);
trait AsReal {
fn as_real(&self) -> Real;
}
impl AsReal for Real { fn as_real(&self) -> Real { *self } }
impl AsReal for Complex { fn as_real(&self) -> Real { self.0 } }
fn project (r : &impl AsReal) -> Complex {
Complex( r.as_real(), Real(0) )
}
fn main() {
let a = Real(1);
let b = Complex(Real(2), Real(3));
let x = project(&a);
let y = project(&b);

println!("{} + {}i", x.0.0, x.1.0);
println!("{} + {}i", y.0.0, y.1.0);
}

但显然,生锈的方法是使用AsRef<Real>代替

#[derive(Clone, Copy)]
struct Real(i32);
struct Complex(Real, Real);
fn project<U: AsRef <Real>>(r : U) -> Complex {
Complex ( *r.as_ref(), Real(0) )
}
impl AsRef<Real> for Complex {
fn as_ref(&self) -> &Real { &self.0 }
}
impl AsRef<Real> for Real {
fn as_ref(&self) -> &Real { self }
}
fn main() {
let a = Real(1);
let b = Complex(Real(2), Real(3));
let x = project(&a);
let y = project(&b);

println!("{} + {}i", x.0.0, x.1.0);
println!("{} + {}i", y.0.0, y.1.0);
}

这让我不满意:project的原型变得非常冗长,难以阅读。如此之多,以至于感觉项目使用的便利根本不值得。

此外,这意味着函数必须选择Complex进入Real强制,我不喜欢这个概念,因为它感觉就像它迫使我防御性地开发并一直使用AsRef<...>

我觉得我没有完全了解,在这种情况下,与rust交互的惯用方法是什么?

根据您的描述,您似乎可以这样做:

  • project占用Real
  • Complex提供了一个into_real()方法,该方法返回Real

小旁注:如果你的类型是小的和Copy,指针并不总是更快。编译器资源管理器是一个很好的工具,可以显示代码段的程序集是什么/

话虽如此,我想这样写。

fn project(real: Real) -> Real {
// very complex logic
}
// deriving Copy makes these types behave more like numbers
#[derive(Copy, Clone)]
struct Real(i32);
#[derive(Copy, Clone)]
struct Complex(Real, Real);
impl Complex {
fn into_real(self) -> Real {
self.0
}
}
fn main() {
let real = Real(0);
let complex = Complex(Real(0), Real(0));
project(real);
project(complex.into_real());
}

如果您真的不喜欢编写into_real(),您可以使调用站点更简单,使声明站点更复杂:

  • Real实现From<Complex>(尽管可以说这需要它自己的特性,因为有不止一种方法从Complex获得Real)
  • 使project接受impl Into<Real>
impl From<Complex> for Real {
fn from(c: Complex) {
c.0
}
}
fn project(real: impl Into<Real>) {
let real = real.into();
// real is a `Real` here
}

老实说,我只会选择第一个选项。您的函数实际上并不需要是泛型的,这增加了单态成本。它不是很面向对象,但是Rust不是一种面向对象的语言。

相关内容

最新更新