< 返回版块

eweca-d 发表于 2021-12-03 13:33

一个简单的代码如下,显然从逻辑上canvas的size和shape的generic的类型无关(假定无关)。当然,实现上由于静态分发是为所有用到的generic生成函数,所以有关。所以才有了这个问题。

求教!在保证静态分发的前提下,要怎么修改,才能使得我可以不指定shape的情况下,获取canvas的size呢?

struct Canvas<T: Shape> {
    shape: T,
    thickness: f64,
}

trait Plane {
    fn get_dimensions() -> usize;
    fn get_volume(&self) -> f64;
}

trait Shape {
    fn get_area(&self) -> f64;
    fn get_diameter(&self) -> f64;
}

struct Circle {
    r: f64,
}

impl Shape for Circle {
    fn get_area(&self) -> f64 {
        self.r * self.r * 3.1415927
    }

    fn get_diameter(&self) -> f64 {
        2.0 * self.r * 3.1415927
    }
}

impl<T: Shape> Plane for Canvas<T> {
    // 仅对于Canvas是3,比如impl Plane for ABC,可能就是2(不用太在意逻辑,大概是这个意思)
    fn get_dimensions() -> usize {
        3
    }

    fn get_volume(&self) -> f64 {
        self.shape.get_area() * self.thickness
    }
}

fn get_canvas_size() {
    // Canvas::get_dimensions();  // error
    Canvas::<Circle>::get_dimensions();  // work,但是显然返回值与shape种类无关,而与canvas有关
}

评论区

写评论
作者 eweca-d 2021-12-06 18:18

作为结题,写一下最终的做法吧。感谢“苦瓜小仔”大佬的思路。最终还是拆成了两个trait,但是为了逻辑上的完整,将二者结合为一个trait。如下,也就是说“WholePlane”是我之后使用该类型时需要用到的trait bound。

fn main() {
    let shape = Circle::new(1.0);
    let canvas = Canvas::new(shape, 2.0);
    let ans = get_test(canvas);
    println!("dimensions: {}", ans);
}

fn get_test<T: WholePlane>(element: T) -> usize {
    println!("V is {}", element.get_volume());
    Canvas::DIMENSIONS
}

trait WholePlane: Plane + PlaneProperties {}
impl<T: Plane + PlaneProperties> WholePlane for T {}

struct Canvas<T: Shape> {
    shape: T,
    thickness: f64,
}

impl<T: Shape> Canvas<T> {
    fn new(shape: T, thickness: f64) -> Self {
        Self {shape, thickness}
    }
}

trait Plane {
    fn get_volume(&self) -> f64;
}

trait PlaneProperties {
    const DIMENSIONS: usize;
}

trait Shape {
    fn get_area(&self) -> f64;
    fn get_diameter(&self) -> f64;
}

struct Circle {
    r: f64,
}

impl Circle {
    fn new(r: f64) -> Self {
        Self { r }
    }
}

impl Shape for Circle {
    fn get_area(&self) -> f64 {
        self.r * self.r * 3.1415927
    }

    fn get_diameter(&self) -> f64 {
        2.0 * self.r * 3.1415927
    }
}

impl PlaneProperties for Canvas<Circle> {
    const DIMENSIONS: usize = 3;
}

impl<T: Shape> Plane for Canvas<T> {
    fn get_volume(&self) -> f64 {
        self.shape.get_area() * self.thickness
    }
}

作者 eweca-d 2021-12-04 17:11

也或许最后就直接放弃,选择动态分发了。除了有一点点的调用费用,动态分发真的挺完美的。

--
👇
苦瓜小仔

作者 eweca-d 2021-12-04 17:07

感谢回答!拓宽了我的思路,trait const这个用法我居然是第一次听说,学习了。

其实倒不是必不必要的问题,因为源代码保证一定有一个实现了trait“Shape”的类似于struct “BasicShape”的存在。所以对于任意这类型的结构体(比如例子中的Canvas和ABC),一定可以使用类似于“Canvas::::get_dimensions()来获取canvas的dimensions”。

实际上Canvas和ABC都impl了一个trait,暂且称之为“Item”。所以需要无差别的处理“T:Item”时,需要得到它的“dimensions”,这个“dimensions”只与“Item”的具体类型(如canvas)有关,而与“Item”具体类型的“Shape”(如这里的Circle)无关。如果每次调用时,都需要显式声明一个impl “Shape”的struct(虽然对于任意struct都获得相同的值),但是总感觉触犯了我的逻辑洁癖。除此之外应该没啥副作用了。

另外,如果使用trait const意味着要引入trait “Dimensions”,此时如果无差别的使用时,就需要“T:Item + Dimensions”。感觉上会变得更麻烦了。

PS:不过最感谢的是让我知道了还有“trait const”这个用法。我决定还是放弃了,但是trait const明显比一个trait function更符合这里的逻辑,所以可能准备改为“Canvas::::Dimensions”来获取吧。

PSS:其实,举例说明,这里的主要核心问题就是,我需要一类impl了trait “Item”的struct,我希望这些struct一定有dimensions这个property或者可以通过trait bound,使得我一定可以通过一个associate function 或者是 associate const来获取这个dimension。

--
👇
苦瓜小仔

苦瓜小仔 2021-12-04 16:12

当然,Canvas::DIMENSIONS 的代价是:你必须引入 Dimension trait 和 WholePlane struct 到作用域。

那么这涉及 trade-off:这种精简的写法是必须的吗。

struct Canvas<T: Shape> {
    shape:     T,
    thickness: f64,
}

trait Plane {
    fn get_dimensions() -> usize;
    fn get_volume(&self) -> f64;
}

trait Shape {
    fn get_area(&self) -> f64;
    fn get_diameter(&self) -> f64;
}

struct Circle {
    r: f64,
}

impl Shape for Circle {
    fn get_area(&self) -> f64 { self.r * self.r * 3.1415927 }

    fn get_diameter(&self) -> f64 { 2.0 * self.r * 3.1415927 }
}

impl<T: Shape> Plane for Canvas<T> {
    fn get_dimensions() -> usize {
        use dimension::*;
        Canvas::DIMENSIONS
    }

    fn get_volume(&self) -> f64 { self.shape.get_area() * self.thickness }
}

// *** added ***
pub mod dimension {
    use super::*;
    pub struct WholePlane;
    impl Shape for WholePlane {
        fn get_area(&self) -> f64 { f64::MAX }

        fn get_diameter(&self) -> f64 { f64::MAX }
    }

    pub trait Dimension {
        const DIMENSIONS: usize;
    }

    impl Dimension for Canvas<WholePlane> {
        const DIMENSIONS: usize = 3;
    }
}

fn get_canvas_size() {
    use dimension::*;
    dbg!(Canvas::DIMENSIONS);
    dbg!(Canvas::<WholePlane>::DIMENSIONS);
    dbg!(Canvas::<Circle>::get_dimensions());
}

fn main() { get_canvas_size(); }
苦瓜小仔 2021-12-04 14:35

我对这块也不是很熟练,但是我觉得有两个地方你可以考虑一下:

get_dimensions 方法不需要 self,所以可以考虑使用 Associated Constants;

如果你想在方法上省略 shape 泛型,那么当某个 trait + 结构体泛型是唯一实现的时候,就可以做到。

struct Canvas<T: Shape> {
    shape: T,
    thickness: f64,
}

trait Plane {
    fn get_volume(&self) -> f64;
}

trait Shape {
    fn get_area(&self) -> f64;
    fn get_diameter(&self) -> f64;
}

struct Circle {
    r: f64,
}

impl Shape for Circle {
    fn get_area(&self) -> f64 {
        self.r * self.r * 3.1415927
    }

    fn get_diameter(&self) -> f64 {
        2.0 * self.r * 3.1415927
    }
}

impl<T: Shape> Plane for Canvas<T> {
    fn get_volume(&self) -> f64 {
        self.shape.get_area() * self.thickness
    }
}

// *** added ***
struct WholePlane;
impl Shape for WholePlane {
    fn get_area(&self) -> f64 {
        f64::MAX
    }

    fn get_diameter(&self) -> f64 {
        f64::MAX
    }
}

trait Dimension {
    const DIMENSIONS: usize;
}

impl Dimension for Canvas<WholePlane> {
    const DIMENSIONS: usize = 3;
}
// *** added ***

fn get_canvas_size() {
    dbg!(Canvas::DIMENSIONS);
    dbg!(Canvas::<WholePlane>::DIMENSIONS);
}

fn main() {
    get_canvas_size();
}
作者 eweca-d 2021-12-03 22:28

非常感谢您的回答!做法很巧妙,但是我的例子举得简单了。我现在稍微改了下例子,实际上shape的实现非常多且复杂,而且impl在一个wrapper上没有意义。

PS:真实的程序比较复杂,我尽可能抽象出一个好理解,简单,但完整的逻辑做为例子,但之前应该没做好。

--
👇
苦瓜小仔

苦瓜小仔 2021-12-03 15:57
struct Canvas<T: Shape> {
    shape: T,
}

// ************* added *************
struct WholeCanvas;

impl Shape for WholeCanvas {}
impl Plane for Canvas<WholeCanvas> {
    fn get_size() -> usize {
        300
    }
}
// ************* added *************

trait Plane {
    fn get_size() -> usize;
}

trait Shape {}

struct Circle {
    r: usize,
}

impl Shape for Circle {}

fn get_canvas_size() {
    dbg!(Canvas::get_size()); // work
    dbg!(Canvas::<WholeCanvas>::get_size()); // work
}
1 共 7 条评论, 1 页