Lazy readonly "property" in Rust - rust

Given this struct in Rust:
struct OrderLine {
price: f32,
quantity: f32,
}
impl OrderLine {
fn total(&self) -> f32 {
println!("total has been computed"); // this is used in the test bellow
self.price * self.quantity
}
}
How can I:
Compute the total value only once per instance of this struct, even when this function is called multiple times (please, see the test bellow for an example of the expected behaviour). The total value must be lazy calculated. I don't want it to be pre-computed when the struct is initialized, for example in an OrderLine::new function.
Maintain consistency between total and the underlining values (price and quantity):
If we allow them to change, total must be recomputed the next time it gets called.
Or, if that is not possible or too difficult, make this struct immutable to prevent changes.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_total_must_be_computed_only_once() {
let order_line = OrderLine {
price: 10.0,
quantity: 2.0,
};
println!("before calling total for the first time");
println!("{}", order_line.total());
println!("before calling total for the second time");
println!("{}", order_line.total());
// The actual output is:
// before calling total for the first time
// total has been computed
// 20
// before calling total for the second time
// total has been computed <- repeated
// 20
// The expected output is:
// before calling total for the first time
// total has been computed <- no repetition
// 20
// before calling total for the second time
// 20
}
}

An alternate approach is to use a OnceCell. The benefit of this (vs the Option approach) is that is does not require &mut self access:
// in the process of being added to the standard library, but not there yet
use once_cell::unsync::OnceCell;
pub struct OrderLine {
price: f32,
quantity: f32,
total: OnceCell<f32>,
}
impl OrderLine {
pub fn new(price: f32, quantity: f32) -> Self {
OrderLine {
price,
quantity,
total: OnceCell::new(),
}
}
pub fn total(&self) -> f32 {
// calculate the total if not already calculated
*self.total.get_or_init(|| {
println!("COMPUTED"); // this is used in the test bellow
self.price * self.quantity
})
}
pub fn set_price(&mut self, price: f32) {
self.price = price;
// clear the previous calculated total
self.total = OnceCell::new();
}
pub fn set_quantity(&mut self, quantity: f32) {
self.quantity = quantity;
// clear the previous calculated total
self.total = OnceCell::new();
}
}
playground

Here's one approach using an Option:
pub struct OrderLine {
price: f32,
quantity: f32,
total: Option<f32>,
}
impl OrderLine {
pub fn new(price: f32, quantity: f32) -> Self {
Self {
price,
quantity,
total: None,
}
}
pub fn total(&mut self) -> f32 {
*self.total.get_or_insert_with(|| {
let total = self.price * self.quantity;
println!("total has been computed"); // this is used in the test bellow
total
})
}
pub fn set_price(&mut self, price: f32) {
self.price = price;
self.total = None;
}
pub fn set_quantity(&mut self, quantity: f32) {
self.quantity = quantity;
self.total = None;
}
pub fn price(&self) -> f32 {
self.price
}
pub fn quantity(&self) -> f32 {
self.quantity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_total_must_be_computed_only_once() {
let mut order_line = OrderLine::new(10.0, 2.0);
println!("before calling total for the first time");
println!("{}", order_line.total());
println!("before calling total for the second time");
println!("{}", order_line.total());
order_line.set_price(500.0);
println!("before calling total for the third time");
println!("{}", order_line.total());
}
}

Related

Rust fast stack implementation without unnecessary memset?

I need a fast stack in Rust. Millions of these need to be created/destroyed per second and each of them need only a fixed depth. I'm trying to squeeze as much speed as I can. I came up with the following (basically textbook stack implementation):
const L: usize = 1024;
pub struct Stack {
xs: [(u64, u64, u64, u64); L],
sz: usize
}
impl Stack {
pub fn new() -> Self {
Self { xs: [(0, 0 ,0, 0); L], sz: 0 }
}
pub fn push(&mut self, item: (u64, u64, u64, u64)) -> bool {
if (self.sz + 1) <= L {
self.xs[self.sz] = item;
self.sz += 1;
true
} else {
false
}
}
pub fn pop(&mut self) -> Option<(u64, u64, u64, u64)> {
(self.sz > 0).then(|| {
self.sz -= 1;
self.xs[self.sz]
})
}
}
The problem is memset, which is unnecessary. So I tried to get rid of it:
pub fn new2() -> Self {
let xs = std::array::from_fn(|_| unsafe { MaybeUninit::uninit().assume_init() });
Self { xs, sz: 0 }
}
This gets rid of the memset, but now I have a warning:
|
18 | let xs = std::array::from_fn(|_| unsafe { MaybeUninit::uninit().assume_init() });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| this code causes undefined behavior when executed
| help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
|
= note: integers must not be uninitialized
= note: `#[warn(invalid_value)]` on by default
If uninitialized integers cause undefined behavior, is it not possible to create this kind of stack, where the logic of the stack guarantees proper behavior and I avoid unnecessary memory operations?
You need to use MaybeUninit all the way through. Change your array to an array of MaybeUninits:
use std::mem::MaybeUninit;
const L: usize = 1024;
pub struct Stack {
xs: [MaybeUninit<(u64, u64, u64, u64)>; L],
sz: usize
}
// From standard library
// https://doc.rust-lang.org/stable/src/core/mem/maybe_uninit.rs.html#350-353
#[must_use]
#[inline(always)]
pub const fn uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N] {
// SAFETY: An uninitialized `[MaybeUninit<_>; LEN]` is valid.
unsafe { MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init() }
}
impl Stack {
pub fn new() -> Self {
Self { xs: uninit_array(), sz: 0 }
}
pub fn push(&mut self, item: (u64, u64, u64, u64)) -> bool {
if (self.sz + 1) <= L {
self.xs[self.sz].write(item);
self.sz += 1;
true
} else {
false
}
}
pub fn pop(&mut self) -> Option<(u64, u64, u64, u64)> {
(self.sz > 0).then(|| {
self.sz -= 1;
// Safety: The value has been initialized
unsafe {
self.xs[self.sz].assume_init()
}
})
}
}

Design patterns without the box

Rust beginner here. I have a number of algorithms that are almost identical but, at the final step, they all aggregate the results in slightly differently ways. Let's say the Algorithm does the following:
pub struct Algorithm<T> {
result_aggregator: Box<dyn ResultAggregator<T>>,
}
impl<T> Algorithm<T> {
pub fn calculate(&self, num1: i32, num2: i32) -> T {
let temp = num1 + num2;
self.result_aggregator.create(temp)
}
}
With this, I can create a few different result aggregator classes to take my temp result and transform it into my final result:
pub trait ResultAggregator<T> {
fn create(&self, num: i32) -> T;
}
pub struct FloatAggregator;
pub struct StringAggregator;
impl ResultAggregator<f32> for FloatAggregator {
fn create(&self, num: i32) -> f32 {
num as f32 * 3.14159
}
}
impl ResultAggregator<String> for StringAggregator {
fn create(&self, num: i32) -> String {
format!("~~{num}~~")
}
}
...and call it like so:
fn main() {
// Here's a float example
let aggregator = FloatAggregator;
let algorithm = Algorithm {
result_aggregator: Box::new(aggregator),
};
let result = algorithm.calculate(4, 5);
println!("The result has value {result}");
// Here's a string example
let aggregator = StringAggregator;
let algorithm = Algorithm {
result_aggregator: Box::new(aggregator),
};
let result = algorithm.calculate(4, 5);
println!("The result has value {result}");
}
This is what I've come up with.
Question: Is it possible to do this without the dynamic box? It's performance critical and I understand that generics are usually a good solution but I've had no luck figuring out how to get it working without dynamic dispatch.
So what's the Rusty solution to this problem? I feel like I'm approaching it with my C# hat on which is probably not the way to go.
Link to the playground
You can use an associated type instead of a generic parameter:
pub trait ResultAggregator {
type Output;
fn create(&self, num: i32) -> Self::Output;
}
pub struct FloatAggregator;
pub struct StringAggregator;
impl ResultAggregator for FloatAggregator {
type Output = f32;
fn create(&self, num: i32) -> f32 {
num as f32 * 3.14159
}
}
impl ResultAggregator for StringAggregator {
type Output = String;
fn create(&self, num: i32) -> String {
format!("~~{num}~~")
}
}
pub struct Algorithm<Aggregator> {
result_aggregator: Aggregator,
}
impl<Aggregator: ResultAggregator> Algorithm<Aggregator> {
pub fn calculate(&self, num1: i32, num2: i32) -> Aggregator::Output {
let temp = num1 + num2;
self.result_aggregator.create(temp)
}
}

How can I make only certain struct fields mutable?

I have a struct:
pub struct Test {
pub x: i32,
pub y: i32,
}
I'd like to have a function that mutates this — easy:
pub fn mutateit(&mut self) {
self.x += 1;
}
This makes the entire struct mutable for the duration of the function call of mutateit, correct? I only want to mutate x, and I don't want to mutate y. Is there any way to just mutably borrow x?
Citing The Book:
Rust does not support field mutability at the language level, so you cannot write something like this:
struct Point {
mut x: i32, // This causes an error.
y: i32,
}
You need interior mutability, which is nicely described in the standard docs:
use std::cell::Cell;
pub struct Test {
pub x: Cell<i32>,
pub y: i32
}
fn main() {
// note lack of mut:
let test = Test {
x: Cell::new(1), // interior mutability using Cell
y: 0
};
test.x.set(2);
assert_eq!(test.x.get(), 2);
}
And, if you wanted to incorporate it in a function:
impl Test {
pub fn mutateit(&self) { // note: no mut again
self.x.set(self.x.get() + 1);
}
}
fn main() {
let test = Test {
x: Cell::new(1),
y: 0
};
test.mutateit();
assert_eq!(test.x.get(), 2);
}

Do Rust builder patterns have to use redundant struct code?

I was looking at the Method syntax section of the Rust documentation and came across an example of the builder pattern. The CircleBuilder struct in the example below is an exact duplicate of the Circle struct. It seems like this redundant code violates the usual norms of programming.
I understand why the example created a new struct, because the creator did not want to implement the builder methods against the original Circle struct. That is fine, but is there a way to rewrite this example so that there is no redundancy--yet still keeping the nice builder interface in the main() function intact?
I tried to create an empty struct or a struct with just one throwaway element, but that did not work.
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
struct CircleBuilder {
x: f64,
y: f64,
radius: f64,
}
impl CircleBuilder {
fn new() -> CircleBuilder {
CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
}
fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
self.x = coordinate;
self
}
fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {
self.y = coordinate;
self
}
fn radius(&mut self, radius: f64) -> &mut CircleBuilder {
self.radius = radius;
self
}
fn finalize(&self) -> Circle {
Circle { x: self.x, y: self.y, radius: self.radius }
}
}
fn main() {
let c = CircleBuilder::new()
.x(1.0)
.y(2.0)
.radius(2.0)
.finalize();
println!("area: {}", c.area());
println!("x: {}", c.x);
println!("y: {}", c.y);
}
Do Rust builder patterns have to use redundant struct code?
No. But sometimes they might. For example, consider if we wanted to have special logic (or even just complicated logic) around our constructor:
/// Width must always be greater than height!
struct HorizontalEllipse {
width: f64,
height: f64,
}
impl HorizontalEllipse {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.width / 2.0) * (self.height / 2.0)
}
}
struct HorizontalEllipseBuilder {
width: f64,
height: f64,
}
impl HorizontalEllipseBuilder {
fn new() -> HorizontalEllipseBuilder {
HorizontalEllipseBuilder {
width: 0.0,
height: 0.0,
}
}
fn width(&mut self, width: f64) -> &mut HorizontalEllipseBuilder {
self.width = width;
self
}
fn height(&mut self, height: f64) -> &mut HorizontalEllipseBuilder {
self.height = height;
self
}
fn finalize(&self) -> Result<HorizontalEllipse, String> {
let HorizontalEllipseBuilder { height, width } = *self;
if height >= width {
Err("This is not horizontal".into())
} else {
Ok(HorizontalEllipse { width, height })
}
}
}
fn main() {
let c = HorizontalEllipseBuilder::new()
.width(1.0)
.height(2.0)
.finalize()
.expect("not a valid ellipse");
println!("area: {}", c.area());
println!("width: {}", c.width);
println!("height: {}", c.height);
}
Now a HorizontalEllipse knows that it is always true that width > height. We've moved that check from many potential places (each method) to one, the constructor. We then moved the constructor to a new type because it was complicated (not really, but truly complicated examples are usually... complicated).
Many builders I've seen also have "enhanced" types of the real object:
#[derive(Debug)]
struct Person {
name: String,
}
#[derive(Debug, Default)]
struct PersonBuilder {
name: Option<String>,
}
impl PersonBuilder {
fn name(self, name: &str) -> Self {
PersonBuilder { name: Some(name.into()), ..self }
}
fn build(self) -> Person {
Person {
name: self.name.unwrap_or_else(|| "Stefani Joanne Angelina Germanotta".into()),
}
}
}
fn main() {
let person = PersonBuilder::default().build();
println!("{:?}", person);
let person = PersonBuilder::default().name("krishnab").build();
println!("{:?}", person);
}
You don't see that in the book's example because it's trying to be simpler and not involve ownership concerns.
This seems like the sort of thing a macro might be able to do. A quick search found the derive_builder and builder_macro crates which seem to implement this functionality.

Passing mutable self reference to method of owned object

The following is a simple simulation with a field which is a rectangular area with two balls bouncing around in it. The Field struct has an update method, which calls update on each of the balls. The balls, in their update method, need to move around based on their velocity. But they also need to react to each other, as well as the boundaries of the field.:
fn main() {
let mut field = Field::new(Vector2d { x: 100, y: 100 });
field.update();
}
#[derive(Copy, Clone)]
struct Vector2d {
x: i32,
y: i32,
}
struct Ball {
radius: i32,
position: Vector2d,
velocity: Vector2d,
}
impl Ball {
fn new(radius: i32, position: Vector2d, velocity: Vector2d) -> Ball {
Ball {
radius: radius,
position: position,
velocity: velocity,
}
}
fn update(&mut self, field: &Field) {
// check collisions with walls
// and other objects
}
}
struct Field {
size: Vector2d,
balls: [Ball; 2],
}
impl Field {
fn new(size: Vector2d) -> Field {
let position_1 = Vector2d {
x: size.x / 3,
y: size.y / 3,
};
let velocity_1 = Vector2d { x: 1, y: 1 };
let position_2 = Vector2d {
x: size.x * 2 / 3,
y: size.y * 2 / 3,
};
let velocity_2 = Vector2d { x: -1, y: -1 };
let ball_1 = Ball::new(1, position_1, velocity_1);
let ball_2 = Ball::new(1, position_2, velocity_2);
Field {
size: size,
balls: [ball_1, ball_2],
}
}
fn update(&mut self) {
// this does not compile
self.balls[0].update(self);
self.balls[1].update(self);
}
}
How do I get the information about the boundaries and the other ball to the Ball struct's update function? These lines in the Field::update do not compile:
self.balls[0].update(self);
self.balls[1].update(self);
Giving the following error:
error[E0502]: cannot borrow `*self` as immutable because `self.balls[..]` is also borrowed as mutable
--> src/main.rs:62:30
|
62 | self.balls[0].update(self);
| ------------- ^^^^- mutable borrow ends here
| | |
| | immutable borrow occurs here
| mutable borrow occurs here
which I understand, but I don't know how to get around this.
Currently your Ball struct needs to know about the Field it's contained in to be able to update itself. This doesn't compile because the result would be cyclic references combined with mutation. You could make this work by using Cell or RefCell (the latter having a performance cost) but it would be even better to structure the code differently. Let the Field struct check for and resolve Ball-Ball and Ball-Wall collisions. The Ball struct's update function can handle updating the Ball's position.
// Ball's update function
fn update(&mut self) {
// update position
}
// Field's update function
fn update(&mut self) {
for ball in self.balls.iter_mut() {
ball.update();
}
// check for collisions
// resolve any collisions
}
Here's a smaller example:
struct Ball {
size: u8,
}
impl Ball {
fn update(&mut self, field: &Field) {}
}
struct Field {
ball: Ball,
}
impl Field {
fn update(&mut self) {
self.ball.update(self)
}
}
The problem
When you pass in a reference to Field, you are making the guarantee that the Field cannot change (the immutable part of "immutable reference"). However, this code is also attempting to mutate a part of it: the ball! Which reference should be authoritative, self or field, in the implementation of Ball::update?
Solution: use only the fields you need
You can separate the parts of the structure needed for update and those not needed and use them before calling the update function:
struct Ball {
size: u8,
}
impl Ball {
fn update(&mut self, field: &u8) {}
}
struct Field {
players: u8,
ball: Ball,
}
impl Field {
fn update(&mut self) {
self.ball.update(&self.players)
}
}
You can even bundle these piecemeal references up into a tidy package:
struct Ball {
size: u8,
}
impl Ball {
fn update(&mut self, field: BallUpdateInfo) {}
}
struct BallUpdateInfo<'a> {
players: &'a u8,
}
struct Field {
players: u8,
ball: Ball,
}
impl Field {
fn update(&mut self) {
let info = BallUpdateInfo { players: &self.players };
self.ball.update(info)
}
}
Or restructure your containing struct to separate the information from the beginning:
struct Ball {
size: u8,
}
impl Ball {
fn update(&mut self, field: &UpdateInfo) {}
}
struct UpdateInfo {
players: u8,
}
struct Field {
update_info: UpdateInfo,
ball: Ball,
}
impl Field {
fn update(&mut self) {
self.ball.update(&self.update_info)
}
}
Solution: remove the member from self
You could also go the other way and remove the Ball from the Field before making any changes to it. If you can easily / cheaply make a Ball, try replacing it:
use std::mem;
#[derive(Default)]
struct Ball {
size: u8,
}
impl Ball {
fn update(&mut self, field: &Field) {}
}
struct Field {
ball: Ball,
}
impl Field {
fn update(&mut self) {
let mut ball = mem::replace(&mut self.ball, Ball::default());
ball.update(self);
self.ball = ball;
}
}
If you can't easily make a new value, you can use an Option and take it:
struct Ball {
size: u8,
}
impl Ball {
fn update(&mut self, field: &Field) {}
}
struct Field {
ball: Option<Ball>,
}
impl Field {
fn update(&mut self) {
if let Some(mut ball) = self.ball.take() {
ball.update(self);
self.ball = Some(ball);
}
}
}
Solution: runtime checks
You can move borrow checking to runtime instead of compile-time via RefCell:
use std::cell::RefCell;
struct Ball {
size: u8,
}
impl Ball {
fn update(&mut self, field: &Field) {}
}
struct Field {
ball: RefCell<Ball>,
}
impl Field {
fn update(&mut self) {
self.ball.borrow_mut().update(self)
}
}

Resources