Rust结构类型别名和“继承”方法

wb1gzix0  于 2023-05-23  发布在  其他
关注(0)|答案(2)|浏览(156)

第一个问题

假设我有以下结构:

#[derive(Copy, Clone, Debug)]
pub struct Vec3 {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

impl Vec3 {
    pub fn dot(self, v: Vec3) -> f64 { ... }
    pub fn add(self, v: Vec3) -> Vec3 { ... }
    ...
}

我希望能够将Vec3的使用区分为Point(表示3D空间中的一个点)和Color(表示RGB值)。第一步是使用类型别名:

pub type Point = Vec3;
pub type Color = Vec3;

然后在代码的其余部分,我将使用类型注解PointColor来建议变量应该是什么。不过,这只是一个建议。没有什么能阻止我输入一个Color作为参数,它的类型提示为Point(因为在一天结束时,它们实际上只是Vec3)。

**第一个问题:**有没有办法强制rust编译器将PointColor作为不同的类型处理?

第二个问题

假设我想实现某些只能在Point上使用的方法,然后实现另一组只能在Color上使用的方法,例如:

impl Point {
    fn point_on_line(&self, t: f64) -> Point { ... }
    ...
}

impl Color {
    fn to_hex(self) -> String { ... }
    ...
}

当然,我仍然希望ColorPoint的示例保留Vec3中的方法。
我能想到的最简单的方法是将Vec3转换为trait,然后为Vec3创建PointColor结构实现:

pub trait Vec3: Copy + Clone + Debug {
    fn dot(self, v: Vec3) -> f64 { ... }
    fn add(self, v: Vec3) -> Vec3 { ... }
    ...

    // now requires the definition of .x(), .y(), and .z() in order
    // to create default implementations for the above methods
    fn x(self) -> f64;
    fn y(self) -> f64;
    fn z(self) -> f64;
}

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

impl Vec3 for Point {
    fn x(self) -> f64 { self.x }
    fn y(self) -> f64 { self.y }
    fn z(self) -> f64 { self.z }
}

当然,这需要trait在默认实现中使用.x()来代替.x

**第二个问题:**是否有一个“更干净”或更习惯的方法来获得上述所需的行为?如果没有,那么通过.x(), .y(), .z()方法进行额外的复制是否会产生任何开销(因为我们是通过一个额外的方法复制值)?我假设后一个问题的答案是否定的,因为编译器会优化它,但这是一个性能关键的应用程序,所以我只是想知道它的确切细节。

cxfofazt

cxfofazt1#

我最喜欢的一个rust技巧是使用generic来对一个结构进行不同的专业化:

#[derive(Copy, Clone, Debug)]
pub struct Vec3<Behavior> {
    pub x: f64,
    pub y: f64,
    pub z: f64,
    behavior: Behavior,
}

pub struct Color;
pub struct Point;

/// not neeeded but help for documentation
trait Vec3Behavior {}
impl Vec3Behavior for Color {}
impl Vec3Behavior for Point {}

impl<Behavior> Vec3<Behavior> {
    pub fn point(x: f64, y: f64, z: f64) -> Vec3<Point> {
        Vec3 {
            x,
            y,
            z,
            behavior: Point,
        }
    }

    pub fn color(x: f64, y: f64, z: f64) -> Vec3<Color> {
        Vec3 {
            x,
            y,
            z,
            behavior: Color,
        }
    }

    pub fn dot(self, v: Self) -> f64 {
        todo!()
    }
    pub fn add(self, v: Self) -> Self {
        todo!()
    }
}

impl Vec3<Point> {
    fn point_on_line(&self, t: f64) -> Self {
        todo!()
    }
}

impl Vec3<Color> {
    fn to_hex(&self) -> String {
        todo!()
    }
}

因此,我不知道你的用例是否有意义,但无论如何。

ffscu2ro

ffscu2ro2#

有没有办法强制rust编译器将Point和Color视为不同的类型?
不。通常的方法是使用newtype/wrapper类型。

pub struct Color(pub Vec3);
pub struct Point(pub Vec3);

如果你想使用trait,你可以有一个返回Vec3的方法,让其余的方法有调用该方法的默认实现。这减少了重复代码的数量。

pub trait Vec3Trait {
    fn vec3(self) -> Vec3;
    fn x(self) -> f64 {
        self.vec3().x
    }
    fn y(self) -> f64 {
        self.vec3().y
    }
    fn z(self) -> f64 {
        self.vec3().z
    }
}

我会说这是惯用的方式。Vec3<Behavior>的答案描述了另一种应该同样好的方法,尽管我不会说它是惯用的,因为文档并没有那么好。
如果没有,通过.x(), .y(), .z()方法进行额外复制是否会产生任何开销
由于这种getter方法被广泛使用,因此很有可能被优化。即使这些是内联的,它也可能导致其他东西不内联,从而降低速度。和往常一样,您需要在真实的数据上对完整的应用程序进行基准测试,以了解它是否有任何效果。

相关问题