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

huangapple go评论57阅读模式
英文:

Rust struct type aliases and "inheriting" methods

问题

第一个问题

有办法强制 Rust 编译器将 PointColor 视为不同的类型吗?

第二个问题

有没有更"清晰"或更符合惯例的方法来实现上述所需的行为?如果没有,通过 .x(), .y(), .z() 方法进行额外的复制是否会导致额外的开销(因为我们通过额外的方法复制了值)?我假设对后面的问题的答案是否定的,因为编译器会对此进行优化,但这是一个性能关键型应用,所以我只是想了解具体的细节。

英文:

First Question


Say I have the following struct:

#[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 { ... }
    ...
}

I want to be able to differentiate the use of Vec3 as either Point, representing a point in 3D space, and Color, representing an RGB value. A first step would be to use type aliases:

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

Then in the rest of my code, I would use the type annotations Point and Color to suggest what a variable is suppose to be. However, it's only a suggestion. Nothing is stopping me from inputing a Color as an argument that is type hinted as Point (since at the end of the day, they are really just Vec3).

First question: Is there a way to force the rust compiler to treat Point and Color as different types?

Second Question


Say I wanted to implement certain methods that can only be used on a Point and then another set of methods that can only be used on Color, for example:

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

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

Of course, I still want instances of Color and Point to retain the methods in Vec3.

The simplest way I can think of doing such would be to convert Vec3 to a trait and then make Point and Color struct implementations for Vec3:

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 }
}

Of course, this would require the trait to use .x() in place of .x everywhere in the default implementations.

Second question: Is there a "cleaner" or more idiomatic approach to get the above desired behaviour? If not, is there any overhead incurred by the extra copy via the .x(), .y(), .z() methods (since we are copying the value through an additional method)? I'm assuming the answer to the latter question is no, in that the compiler will optimize that away, but this is for a performance critical application so I'm just wondering about the exact details of that.

答案1

得分: 1

以下是您提供的代码部分的翻译:

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

/// 不是必需的,但有助于文档
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!()
    }
}

请注意,中文中的代码内容与英文中的内容相同,只是进行了翻译。如果您需要更多帮助,请随时提出。

英文:

One of my favorite rust trick is to use generic to have different specialization of one structure:

#[derive(Copy, Clone, Debug)]
pub struct Vec3&lt;Behavior&gt; {
    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&lt;Behavior&gt; Vec3&lt;Behavior&gt; {
    pub fn point(x: f64, y: f64, z: f64) -&gt; Vec3&lt;Point&gt; {
        Vec3 {
            x,
            y,
            z,
            behavior: Point,
        }
    }

    pub fn color(x: f64, y: f64, z: f64) -&gt; Vec3&lt;Color&gt; {
        Vec3 {
            x,
            y,
            z,
            behavior: Color,
        }
    }

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

impl Vec3&lt;Point&gt; {
    fn point_on_line(&amp;self, t: f64) -&gt; Self {
        todo!()
    }
}

impl Vec3&lt;Color&gt; {
    fn to_hex(&amp;self) -&gt; String {
        todo!()
    }
}

Thus I don't know if your use case make much sense but anyway.

答案2

得分: 0

> 有没有一种方法可以强制 Rust 编译器将 Point 和 Color 视为不同类型?

没有。通常的做法是使用新类型/包装类型。

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() 方法进行额外拷贝是否会产生额外开销?

由于这种类型的获取方法被广泛使用,很可能会被优化。即使这些方法被内联,可能会导致其他部分不被内联,降低速度。像往常一样,你需要在真实数据上对完整应用程序进行基准测试,以了解它是否有任何影响。

英文:

> Is there a way to force the rust compiler to treat Point and Color as different types?

No. The usual way to do this is with a newtype/wrapper type.

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

If you want to use a trait, you can have one method that returns a Vec3 and have the rest have default implementations that call that method. This lessens the amount of duplicate code.

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

I would say this is the idiomatic way. The Vec3&lt;Behavior&gt; answer describes another way that should be just as good, although I wouldn't say it's idiomatic since the documentation doesn't end up as nice.

> If not, is there any overhead incurred by the extra copy via the .x(), .y(), .z() methods

Since this kind of getter method is used extensively, it's very likely to be optimized. Even if these are inlined, it might cause something else to not be inlined, reducing speed. As always, you would need to benchmark your full application on real data to know if it has any effect.

huangapple
  • 本文由 发表于 2023年5月18日 06:31:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76276592.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定