如何在您创建的数据需要引用的地方创建工厂函数?

How to create a factory function where the data you're creating requires references?

在某些情况下,您希望工厂函数创建一些数据,但出于某种原因,您 return 必须 包含引用的数据。这似乎不可能,因为您不能 return 从函数引用。

让我们看下面的示例代码:

// Important: traits are of unknown size, and so must be passed around 
// as a reference
trait Shape {
    fn area(&self) -> f32;
}
// As seen here.
struct ShapeCollection<'a> {
    shapes: Vec<&'a Shape>
}

// This function needs to return a ShapeCollection, but since the
// data structure requires references to work, it seems impossible!
fn make_shapes<'a>(needRectangle: bool, multiplier: f32) -> ShapeCollection<'a> {
    let rect = Rectangle {
        width: 42.0 * multiplier,
        height: 24.0 * multiplier
    };
    let circle = Circle {
        radius: 42.0 * multiplier
    };

    match needRectangle {
        true => ShapeCollection {
            shapes: vec![&rect, &circle]
        },
        false => ShapeCollection {
            shapes: vec![&circle]
        },
    }
}
// ^ This function won't compile because rect and circle do not
//   life long enough, and the compiler dies at this point

// Impls if you're interested / want to compile, but not that important
struct Rectangle {
    width: f32,
    height: f32
}
impl Shape for Rectangle {
    fn area(&self) -> f32 {
        self.width * self.height
    }
}
struct Circle {
    radius: f32
}
impl Shape for Circle {
    fn area(&self) -> f32 {
        (std::f32::consts::PI * self.radius).powf(2f32)
    }
}

这是一个基于我正在编写的更复杂代码构建的简化示例。问题的要点是两个混淆的要求:

如何在 Rust 中解决这两个要求?

两个选择好像是:

一种可能性似乎是使用枚举而不是特征来实现多态性。

对于此示例,它将是:

enum Shape2 {
    Rectangle { width: f32, height: f32 },
    Circle { radius: f32 }
}
fn area(shape: Shape2) -> f32 {
    match shape {
        Shape2::Rectangle {width, height} => width * height,
        Shape2::Circle  {radius} => (std::f32::consts::PI * radius).powf(2f32)
    }
}
struct Shape2Collection {
    shapes: Vec<Shape2>
}

fn make_shapes2(needRectangle: bool, multiplier: f32) -> Shape2Collection {
    let rect = Shape2::Rectangle {
        width: 42.0 * multiplier,
        height: 24.0 * multiplier
    };
    let circle = Shape2::Circle {
        radius: 42.0 * multiplier
    };

    match needRectangle {
        true => Shape2Collection {
            shapes: vec![rect, circle]
        },
        false => Shape2Collection {
            shapes: vec![circle]
        },
    }
}

这样做的缺点是您的实现不再是方法,而是函数,并且您的实现被混杂在同一个函数中。

你只需cannot return a reference to a variable created in a function.

Important: traits are of unknown size, and so must be passed around as a reference

这不完全正确。它们必须通过某种间接方式传递,但引用 (&) 并不是 trait 对象 可以使用的唯一一种间接方式。还有盒装特征对象:

struct ShapeCollection {
    shapes: Vec<Box<Shape>>,
}

fn make_shapes(need_rectangle: bool, multiplier: f32) -> ShapeCollection {
    let rect = Rectangle {
        width: 42.0 * multiplier,
        height: 24.0 * multiplier,
    };
    let circle = Circle {
        radius: 42.0 * multiplier,
    };

    match need_rectangle {
        true => ShapeCollection {
            shapes: vec![Box::new(rect), Box::new(circle)],
        },
        false => ShapeCollection {
            shapes: vec![Box::new(circle)],
        },
    }
}

在 99.9% 的情况下,生命周期仅在 return 位置的函数签名永远无法工作(或者不做你想做的事)并且应该 always给你暂停:

fn make_shapes<'a>(need_rectangle: bool, multiplier: f32) -> ShapeCollection<'a>

请注意,Rust 样式是 snake_case,因此我重命名为 need_rectangle

另请参阅:

  • What makes something a "trait object"?
  • Is there any way to return a reference to a variable created in a function?