2

There are situations where you want a factory function to create some data, but for whatever reasons the data you return must contain references. This does not seem possible because you cannot return references from functions.

Let's look at the following example code:

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

This is a simplified example built out of a more complex code I am writing. The gist of the issue is two conflating requirements:

  • I need polymorphism. Since I'm using traits for this, and traits are not sized, the Vec must contain references not values
  • I need a factory function. Since I want to generate this list of objects from multiple parts of my code but with different parameters, it makes sense to make a function that encapsulates that, instead of copying and pasting logic around

How can I solve both of these requirements in Rust?

The two choices seems to be:

  • Do something else to achieve polymorphism, something that is allowed to be passed around as a value
  • De-duplicate multiple code blocks in some way that isn't creating a parameterised function
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
SCdF
  • 57,260
  • 24
  • 77
  • 113

2 Answers2

2

You simply 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

This isn't strictly true. They must be passed around via some kind of indirection, but a reference (&) isn't the only kind of indirection that a trait object can use. There's also boxed trait objects:

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)],
        },
    }
}

In 99.9% of the cases, a function signature with a lifetime that's only in the return position can never work (or doesn't do what you want) and should always give you pause:

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

Note that the Rust style is snake_case, so I've renamed need_rectangle.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
0

One possibility seems to be to use enums instead of traits for polymorphism.

For this example that would be:

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

The downside of this is that your implementations aren't methods anymore, but are functions, and that your implementations are smushed together in the same function.

SCdF
  • 57,260
  • 24
  • 77
  • 113