Collection with Traits that return Self - rust

I'm trying to have a collection of objects that implement a particular trait.
If I use a trait that returns a value, this works
use std::collections::BTreeMap;
struct World {
entities: Vec<usize>,
database: BTreeMap<usize, Box<ReadValue>>,
//database : BTreeMap<usize,Box<ReadEcs>>, // Doesn't work
}
struct SourceInputGateway {
entity_id: usize,
}
trait ReadValue {
fn read(&self) -> f32;
}
impl ReadValue for SourceInputGateway {
fn read(&self) -> f32 {
0.0
}
}
But if I want to return Self as a value then this doesn't work, either as a method template param or associated type
trait ReadEcs {
type T;
fn read(&self) -> &Self::T;
}
impl ReadEcs for SourceInputGateway {
type T = SourceInputGateway;
fn read(&self) -> &Self::T {
self
}
}
What I would like to do is have a map of types that implement ReadEcs, the concrete type of which is not important.
Further clarification edit
If I expand the example by adding
// Different sized type
struct ComputeCalculator {
entity_id : usize,
name : String,
}
impl ReadValue for ComputeCalculator {
fn read(&self) -> f32 {
1230.0
}
}
then I can do this
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_write() {
let mut world = World::new();
world.database.insert(0,Box::new(SourceInputGateway{ entity_id : 1}));
world.database.insert(2,Box::new(ComputeCalculator{ entity_id : 2 , name : "foo".into() }));
for (k,ref v) in world.database {
let item : &Box<ReadValue> = v;
item.read();
}
}
}
but if I change or add a trait method that returns Self, I can't do this.
I'd like to understand a way to get around it without unsafe pointers.

I thought about this a little more and I think it's possible to solve this while retaining all the advantages of type safety and contiguous storage.
Defining a entity manager with pointers to storage
struct Entities {
entities: Vec<usize>,
containers: Vec<Box<Storage>>,
}
The components themselves that store data related to behaviour
struct Position {
entity_id: usize,
position: f32,
}
struct Velocity {
entity_id: usize,
velocity: f32,
}
We will have many instances of components, so we need a contiguous memory store, accessed by an index.
struct PositionStore {
storage: Vec<Position>,
}
struct VelocityStore {
storage: Vec<Velocity>,
}
trait Storage {
// Create and delete instances of the component
fn allocate(&mut self, entity_id: usize) -> usize;
// Interface methods that would correspond to a base class in C++
fn do_this(&self);
// fn do_that(&self);
}
The trait for the store implements an arena style storage plus the methods it would pass to to the components. This would likely be in the "System" part of an ECS but is left as an exercise for later.
I would like a hint as to how to pass a Fn() that constructs a custom object to the allocate() method. I've not figured that out yet.
impl Storage for PositionStore {
fn allocate(&mut self, entity_id: usize) -> usize {
self.storage.push(Position {
entity_id,
position: 0.0,
});
self.storage.len() - 1
}
fn run(&self) {
self.storage.iter().for_each(|item| { println!("{}",item.position); });
}
}
impl Storage for VelocityStore {
fn allocate(&mut self, entity_id: usize) -> usize {
self.storage.push(Velocity {
entity_id,
velocity: 0.0,
});
self.storage.len() - 1
}
fn do_this(&self) {
self.storage.iter().for_each(|item| { println!("{}",item.velocity); });
}
}
Some boiler plate.
impl Default for PositionStore {
fn default() -> PositionStore {
PositionStore {
storage: Vec::new(),
}
}
}
impl Default for VelocityStore {
fn default() -> VelocityStore {
VelocityStore {
storage: Vec::new(),
}
}
}
I think this could be thought about a little more. It stores the relationship between the storage of the components and their position
Instead of T::default(), you may want to pass a lambda function that has a specific initialisation for each of your components
impl Entities {
fn register<T>(&mut self) -> usize
where
T: Storage + Default + 'static,
{
self.containers.push(Box::new(T::default()));
self.containers.len() - 1
}
fn create<T>(&mut self, entity_id: usize, id: usize) -> usize {
self.containers[id].allocate(entity_id)
}
fn run_loop(&self) {
self.containers.iter().for_each(|x| x.do_this());
}
}
A test case to see if this works
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arbitary() {
let mut entities = Entities {
entities: Vec::new(),
containers: Vec::new(),
};
let velocity_store_id = entities.register::<VelocityStore>();
let position_store_id = entities.register::<PositionStore>();
let _ = entities.create::<Velocity>(123, velocity_store_id);
let _ = entities.create::<Velocity>(234, velocity_store_id);
let _ = entities.create::<Position>(234, position_store_id);
let _ = entities.create::<Position>(567, position_store_id);
entities.run_loop();
}
}

Related

How to use the typestate pattern in other struct

I want to use the typestate pattern to define several states that allow some exclusive operations on each of them.
I'm using traits instead of an enum to allow further customizations.
So, I'm able to use this pattern until I try to include it inside a struct (the Session part) that is mutated when files are added, changed or removed.
trait IssueState {}
struct Open;
impl IssueState for Open {}
struct WIP {
elapsed_time: u32,
}
impl IssueState for WIP {}
struct Closed {
elapsed_time: u32,
}
impl IssueState for Closed {}
struct Issue<T: IssueState + ?Sized> {
state: Box<T>,
comments: Vec<String>,
}
impl<T: IssueState> Issue<T> {
pub fn comment<S: Into<String>>(&mut self, comment: S) -> &mut Self {
self.comments.push(comment.into());
self
}
}
impl Issue<Open> {
pub fn new() -> Self {
Self {
state: Box::new(Open),
comments: vec![],
}
}
pub fn start(self) -> Issue<WIP> {
Issue {
state: Box::new(WIP { elapsed_time: 0 }),
comments: self.comments,
}
}
}
impl Issue<WIP> {
pub fn work(&mut self, time: u32) -> &mut Self {
self.state.elapsed_time += time;
self
}
pub fn done(self) -> Issue<Closed> {
let elapsed_time = self.state.elapsed_time;
Issue {
state: Box::new(Closed { elapsed_time }),
comments: self.comments,
}
}
}
impl Issue<Closed> {
pub fn elapsed(&self) -> u32 {
self.state.elapsed_time
}
}
struct Session<T: IssueState> {
user: String,
current_issue: Issue<T>,
}
impl<T: IssueState> Session<T> {
pub fn new<S: Into<String>>(user: S, issue: Issue<T>) -> Self {
Self {
user: user.into(),
current_issue: issue,
}
}
pub fn comment<S: Into<String>>(&mut self, comment: S) {
self.current_issue.comment(comment);
}
}
impl Session<WIP> {
pub fn work(&mut self, time: u32) {
self.current_issue.work(time);
}
}
trait Watcher {
fn watch_file_create(&mut self);
fn watch_file_change(&mut self);
fn watch_file_delete(&mut self);
}
impl<T: IssueState> Watcher for Session<T> {
fn watch_file_create(&mut self) {
self.current_issue = Issue::<Open>::new();
}
fn watch_file_change(&mut self) {}
fn watch_file_delete(&mut self) {}
}
fn main() {
let open = Issue::<Open>::new();
let mut wip = open.start();
wip.work(10).work(30).work(60);
let closed = wip.done();
println!("Elapsed {}", closed.elapsed());
let mut session = Session::new("Reviewer", closed);
session.comment("It is OK");
session.watch_file_create();
}
Rust Playground (original)
Rust Playground (edited)
What can I do to fix the problems?
Is the typestate pattern limited to only some situations that do not depend a lot on external events? I mean, I'm trying to use it for processing events, but is it a dead end?, why?
Your Session has a Issue<dyn IssueState> member, but you want to implement its work method by calling Issue<WIP>'s work method. The compiler complains, because an Issue<dyn IssueState> is not (necessarily) a Issue<WIP> and so does not implement that method.

The proper ownership for "caching proxy" in Rust?

I'd like to use Factory to build an object from the String and have multiple impls: 1) actual building and 2) caching (stores in-memory in HashMap). The problem is that in case #1 it have to pass the ownership and in case #2 HashMap owns the value and a reference can be returned only.
use std::collections::HashMap;
// product interface
pub trait TProduct {
fn get_title(&self) -> &String;
}
// and concrete impls
pub struct ConcreteProduct1 {
}
impl TProduct for ConcreteProduct1 {
// ...
}
pub struct ConcreteProduct2 {
}
impl TProduct for ConcreteProduct2 {
// ...
}
// factory interface
pub trait TProductFactory {
fn product_from_text(&mut self, text: String) -> Box<dyn TProduct>;
// QUESTION: should it be Box (required for ProductFactory) or &Box (required for ProductCachingProxy)?
}
// actual building factory
pub struct ProductFactory {
}
impl TProductFactory for ProductFactory {
fn product_from_text(&mut self, text: String) -> Box<dyn TProduct> {
//...
// depending on some conditions
Box::new(ConcreteProduct1::from_text(text)); // has to pass the ownership
// or
Box::new(ConcreteProduct2::from_text(text)); // has to pass the ownership
//...
}
}
// caching proxy
trait TProductCache {
fn put(&mut self, text: &String, product: Box<dyn TProduct>);
fn get(&self, text: &String) -> Option<&Box<dyn TProduct>>;
fn clear(&mut self);
}
struct InMemoryProductCache {
map: HashMap<String, Box<dyn TProduct>>
}
impl InMemoryProductCache {
fn new() -> Self {
return InMemoryProductCache {
map: HashMap::new()
}
}
}
impl TProductCache for InMemoryProductCache {
fn put(&mut self, text: &String, product: Box<dyn TProduct>) {
self.map.insert(text.to_string(), product);
}
fn get(&self, text: &String) -> Option<&Box<dyn TProduct>> {
return match self.map.get(text) {
Some(boxed_product) => Some(boxed_product), // have to pass a reference to let it still own the value
None => None
}
}
fn clear(&mut self) {
self.map.clear();
}
}
struct ProductCachingProxy {
product_factory: Box<dyn TProductFactory>,
cache: Box<dyn TProductCache>
}
impl ProductCachingProxy {
fn new_for_factory(product_factory: Box<dyn TProductFactory>, cache: Box<dyn TProductCache>) -> Self {
return ProductCachingProxy {
product_factory,
cache
}
}
}
impl TProductFactory for ProductCachingProxy {
fn product_from_text(&mut self, text: String) -> &Box<dyn TProduct> { // can't pass ownership
let boxed_product = match self.cache.get(&text) {
Some(found_boxed_product) => found_boxed_product,
_ => {
// delegate creation to wrapped TProductFactory impl (`product_factory`)
let boxed_product = self.product_factory.product_from_text(text.clone());
// ... and put to the cache
self.cache.put(&text, boxed_product);
&boxed_product
}
};
return boxed_product;
}
}
// QUESTION: should it be Box (required for ProductFactory) or &Box (required for ProductCachingProxy) to be returned from TProductFactory.fn product_from_text(&mut self, text: String) -> Box<dyn TProduct>; ?
If caching proxy to return a Box, how can it be created from a reference without copying/cloning (TProductCache.get(..))?
Replace Box with Rc (or Arc if you use threads). It provides shared ownership and suites both your cases with single signature. Another option is to use Cow that is a enum of owned and borrowed states.

Is this the right way to capture a string by value?

I'm making a functional builder that configures an object such as:
struct Person
{
name: String,
position: String
}
The builder itself keeps a list of boxed closures to be applied to the object when it needs to be constructed:
struct FunctionalBuilder<TSubject>
where TSubject : Default
{
actions: Vec<Box<dyn Fn(&mut TSubject) -> ()>>
}
impl<TSubject> FunctionalBuilder<TSubject>
where TSubject : Default
{
fn build(self) -> TSubject
{
let mut subj = TSubject::default();
for action in self.actions
{
(*action)(&mut subj);
}
subj
}
}
The idea being that one can aggregate this builder and then customize it for an object such as Person. Now, let's say I want to have a builder method called() that takes a name and saves the assignment of the name in the closure. I implement it as follows:
impl PersonBuilder
{
pub fn called(mut self, name: &str) -> PersonBuilder
{
let value = name.to_string();
self.builder.actions.push(Box::new(move |x| {
x.name = value.clone();
}));
self
}
}
Is this the right way of doing things? Is there a better way that avoids the temporary variable and clone() call?
Complete working example:
#[derive(Debug, Default)]
struct Person {
name: String,
position: String,
}
struct FunctionalBuilder<TSubject>
where
TSubject: Default,
{
actions: Vec<Box<dyn Fn(&mut TSubject) -> ()>>,
}
impl<TSubject> FunctionalBuilder<TSubject>
where
TSubject: Default,
{
fn build(self) -> TSubject {
let mut subj = TSubject::default();
for action in self.actions {
(*action)(&mut subj);
}
subj
}
fn new() -> FunctionalBuilder<TSubject> {
Self {
actions: Vec::new(),
}
}
}
struct PersonBuilder {
builder: FunctionalBuilder<Person>,
}
impl PersonBuilder {
pub fn new() -> Self {
PersonBuilder {
builder: FunctionalBuilder::<Person>::new(),
}
}
pub fn called(mut self, name: &str) -> PersonBuilder {
let value = name.to_string();
self.builder.actions.push(Box::new(move |x| {
x.name = value;
}));
self
}
pub fn build(self) -> Person {
self.builder.build()
}
}
pub fn main() {
let builder = PersonBuilder::new();
let me = builder.called("Dmitri").build();
println!("{:?}", me);
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=27eb6283836a478d5c68aa025aa4698d
You already do it, value is owned by your closure, the problem is that you require the Fn trait. This mean that action (the function) need to be able to be called many times. This mean value need to be cloned to keep it valid inside the closure. The closure can't give away its ownership.
One way would be to use FnOnce, which would make it possible to remove the clone, but this means that the builder can only be used once. To do this, use actions: Vec<Box<dyn FnOnce(&mut TSubject) -> ()>>, and action(&mut subj);.
More:
"cannot move a value of type FnOnce" when moving a boxed function
Fn* closure traits implemented for Box<dyn Fn*>

Why does variable need to continue living after output is consumed?

use std::cell::RefCell;
use std::collections::VecDeque;
// minimum reproducible example for stack overflow question
// which is essentially the whole thing b/c all of these types
// depend on each other
// this is an implementation of Monopoly Deal (see http://monopolydealrules.com/)
// but the only relevant part to the question is in main(), around line 400
// card.rs
pub trait Card {
fn value(&self) -> i32;
fn kind(&self) -> CardKind;
}
pub enum CardKind {
Money,
Action,
Property,
Building,
}
// property.rs
pub struct PropertySet {
color: Color,
properties: Vec<Property>,
house: bool,
hotel: bool,
}
impl PropertySet {
pub fn rent(&self) -> i32 {
unimplemented!() // stub
}
pub fn color(&self) -> Color {
self.color
}
pub fn properties(&self) -> &Vec<Property> {
&self.properties
}
pub fn is_full(&self) -> bool {
self.properties().len() == self.color.set_size() as usize
}
pub fn is_empty(&self) -> bool {
self.properties().len() == 0
}
pub fn add(&mut self, property: Property) -> Result<(), PropertySetAddError> {
if !property.matches(self.color) {
return Err(PropertySetAddError {
property,
kind: PropertySetAddErrorKind::WrongColor,
});
}
if self.properties.len() + 1 < self.color.set_size() as usize {
self.properties.push(property);
Ok(())
} else {
Err(PropertySetAddError {
property,
kind: PropertySetAddErrorKind::SetFull,
})
}
}
fn remove(&mut self, index: usize) -> Property {
self.properties.remove(index)
}
}
pub struct PropertySetAddError {
property: Property,
kind: PropertySetAddErrorKind,
}
pub enum PropertySetAddErrorKind {
WrongColor,
SetFull,
}
pub enum Property {
Basic { name: String, color: Color },
Wild { colors: [Color; 2], value: i32 },
Any,
}
impl Card for Property {
fn value(&self) -> i32 {
unimplemented!() // stub
}
fn kind(&self) -> CardKind {
CardKind::Property
}
}
impl Property {
pub fn matches(&self, color: Color) -> bool {
unimplemented!() // stub
}
}
#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub enum Color {
Red,
Orange,
Yellow,
Green,
LightBlue,
DarkBlue,
Magenta,
Brown,
Utility,
Railroad,
}
impl Color {
pub fn set_size(&self) -> i32 {
unimplemented!() // stub
}
}
// money.rs
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Money(pub i32);
impl Card for Money {
fn value(&self) -> i32 {
self.0
}
fn kind(&self) -> CardKind {
CardKind::Money
}
}
#[derive(Copy, Clone, Hash, Eq, PartialEq)]
pub struct Id(pub usize);
pub struct Possessions {
bank: Vec<Money>,
hand: Vec<Box<dyn Card>>,
properties: Vec<PropertySet>,
}
// brain.rs
pub trait Brain {
fn act(&self, state: &GameState, possessions: &mut Possessions) -> Vec<Effect>;
fn react(
&self,
state: &GameState,
possessions: &mut Possessions,
effect: &Effect,
) -> Vec<Effect>;
}
pub struct Human {}
impl Human {
pub fn new() -> Human {
Human {}
}
}
impl Brain for Human {
fn act(&self, state: &GameState, possessions: &mut Possessions) -> Vec<Effect> {
unimplemented!()
}
fn react(
&self,
state: &GameState,
possessions: &mut Possessions,
effect: &Effect,
) -> Vec<Effect> {
unimplemented!()
}
}
// action.rs
pub enum Action {
Rent { colors: [Color; 2] },
AnyRent,
DoubleRent,
DealBreaker,
DebtCollector,
Birthday,
SayNo,
PassGo,
SlyDeal,
ForcedDeal,
}
impl Card for Action {
fn value(&self) -> i32 {
unimplemented!() // stub
}
fn kind(&self) -> CardKind {
CardKind::Action
}
}
impl Action {
pub fn effect<'a>(
&self,
source: Id,
target: ActionTarget<'a>,
) -> Result<Effect<'a>, EffectError> {
unimplemented!() // stub
}
}
pub enum ActionTarget<'a> {
None,
Player {
player: Id,
},
Set {
/// The player that this action is targeted against
player: Id,
/// Can be a set that player owns, such as in the case of Deal Breaker, or it
/// can be a set that the source owns, such as in the case of rent cards
set: &'a PropertySet,
},
Property {
player: Id,
property: (&'a PropertySet, usize),
},
Swap {
player: Id,
take: (&'a PropertySet, usize),
give: (&'a PropertySet, usize),
},
}
// effect.rs
#[derive(Clone)]
pub enum Effect<'a> {
// swaps properties with another player
Swap {
source: Id,
target: Id,
give: (&'a PropertySet, usize),
take: (&'a PropertySet, usize),
},
// steals properties from another player
Steal {
source: Id,
target: Id,
set: &'a PropertySet,
indices: Vec<usize>,
},
// charges another player
Charge {
source: Id,
target: Option<Id>,
amount: i32,
reason: ChargeReason,
},
// cancels prior effect
Cancel {
source: Id,
target: Id,
},
// pass Go, credit $2M
Go {
source: Id,
},
}
#[derive(Clone)]
pub enum ChargeReason {
Rent,
Birthday,
DebtCollector,
}
pub enum EffectError {
/// The target supplied was not valid for this action.
InvalidTarget,
/// You tried to charge rent for a color, but the specified set isn't that color
NoProperty,
}
// player.rs
pub struct Player {
pub id: Id,
pub name: String,
pub possessions: Possessions,
pub brain: Box<dyn Brain>,
}
impl Player {
pub fn new(id: Id, name: String, brain: Box<dyn Brain>) -> Player {
Player {
id,
possessions: Possessions {
bank: vec![],
hand: vec![],
properties: vec![],
},
name,
brain,
}
}
pub fn id(&self) -> Id {
self.id
}
pub fn bank(&self) -> &Vec<Money> {
&self.possessions.bank
}
pub fn properties(&self) -> &Vec<PropertySet> {
&self.possessions.properties
}
pub fn name(&self) -> &str {
&self.name
}
pub fn brain(&self) -> &Box<dyn Brain> {
&self.brain
}
pub fn act(&mut self, state: &GameState) -> Vec<Effect> {
self.brain.act(state, &mut self.possessions)
}
pub fn react(&mut self, state: &GameState, effect: &Effect) -> Vec<Effect> {
self.brain.react(state, &mut self.possessions, effect)
}
}
// state.rs
pub struct GameState {
pub players: Vec<RefCell<Player>>,
pub current: Id,
pub stack: Vec<Box<dyn Card>>,
}
impl GameState {
pub fn next(&mut self) {
self.current = Id((self.current.0 + 1) % self.players.len())
}
pub fn current_player(&self) -> &RefCell<Player> {
self.players.get(self.current.0).unwrap()
}
pub fn get_player(&self, id: Id) -> Option<&RefCell<Player>> {
self.players.get(id.0)
}
}
// deck.rs
pub fn create_deck() -> Vec<Box<dyn Card>> {
vec![
// ...
]
}
// main.rs
pub fn main() {
let brain = Human::new(); // Human implements Brain
let me = Player::new(Id(0), "Ibi".to_owned(), Box::new(brain));
let current = me.id();
let players = vec![RefCell::new(me)];
let stack = create_deck(); // returns Vec<Box<dyn Card>>
let mut state = GameState {
players,
stack,
current,
};
while !state.players.iter().any(|player| {
player
.borrow()
.properties()
.iter()
.filter(|&set| set.is_full())
.count()
>= 3
}) {
// ...
let mut effects = VecDeque::new();
{
let mut current_player = state.current_player().borrow_mut();
let action = current_player.act(&state);
effects.extend(action);
}
// let all other players react to effects until no more reactions are generated
while !effects.is_empty() {
let effect = effects.pop_front().unwrap();
// ...
}
}
}
I can't get this code to compile; it says that current_player does not live long enough and complains that it is dropped at the end of the block. Why does current_player need to survive beyond the end of the block? My understanding is that it returns a Vec<Effect> which my method now owns, and then that vector is consumed by extend() and the values it contained are now owned by effects, so why is current_player still necessary?

Cannot move out of borrowed content and Builder pattern

I am just learning Rust. I am trying to create a builder struct for my Game struct. Here is the code:
struct Input {
keys_pressed: HashMap<VirtualKeyCode, bool>,
}
pub struct GameBuilder {
settings: GameSettings,
input: Input,
}
impl GameBuilder {
pub fn new() -> GameBuilder {
GameBuilder {
settings: GameSettings {
window_dimensions: (800, 600),
title: "".to_string(),
},
input: Input {
keys_pressed: HashMap::new(),
}
}
}
pub fn with_dimensions(&mut self, width: u32, height: u32) -> &mut GameBuilder {
self.settings.window_dimensions = (width, height);
self
}
pub fn with_title(&mut self, title: &str) -> &mut GameBuilder {
self.settings.title = title.to_string();
self
}
pub fn game_keys(&mut self, keys: Vec<VirtualKeyCode>) -> &mut GameBuilder {
for key in keys {
self.input.keys_pressed.insert(key, false);
}
self
}
pub fn build(&self) -> Game {
let (width, height) = self.settings.window_dimensions;
Game {
display: glutin::WindowBuilder::new()
.with_dimensions(width, height)
.with_title(self.settings.title.to_string())
.build_glium()
.ok()
.expect("Error in WindowBuilder"),
state: GameState::Running,
input: self.input,
}
}
}
But this code complains in the last line input: self.input with this:
error: cannot move out of borrowed content
I think I understand why. Since the argument passed in the function is &self, I cannot take ownership of it, and that what the last line is doing.
I thought that maybe changing &self to self would work, but then the compile argues that I cannot mutate self.
There is also the Copy trait from what I know, and that maybe should solve the problem. But Input is basically a HashMap, which means that a copy could be expensive if the hash itself is too big.
How would be a nice way of solving this problem?
Edit:
I tried doing this:
#[derive(Debug, Copy, Clone)]
struct Input {
keys_pressed: HashMap<VirtualKeyCode, bool>,
}
But the compiler complains:
error: the trait `Copy` may not be implemented for this type; field `keys_pressed` does not implement `Copy`
Given how your method signatures are formulated, you appear to be aiming for chaining:
let game = GameBuilder::new().with_dimensions(...)
.with_title(...)
.build();
In Rust, this requires that GameBuilder be passed by value:
pub fn with_dimensions(self, ...) -> GameBuilder {
// ...
}
And in order to be able to mutate self within the method, you need to make it mut:
pub fn with_dimensions(mut self, ...) -> GameBuilder {
}
If you change the signature of with_dimensions, with_title, game_keys and build to take self by value (mut self if mutation is intended), then chaining should work.
Try the builder pattern with Option and take()
Example:
#[derive(PartialEq, Debug)]
struct Game {
window: Window,
}
#[derive(PartialEq, Debug)]
struct Window {
title: String,
dimensions: (u32, u32),
}
struct GameBuilder {
window_title: Option<String>,
window_dimensions: Option<(u32, u32)>,
}
impl GameBuilder {
fn new() -> Self {
Self {
window_title: None,
window_dimensions: None,
}
}
fn window_title(&mut self, window_title: &str) -> &mut Self {
self.window_title = Some(window_title.to_owned());
self
}
fn window_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
self.window_dimensions = Some((width, height));
self
}
fn build(&mut self) -> Result<Game, Box<dyn std::error::Error>> {
Ok(Game {
window: Window {
// `ok_or(&str)?` works, because From<&str> is implemented for Box<dyn Error>
title: self.window_title.take().ok_or("window_title is unset")?,
dimensions: self
.window_dimensions
.take()
.ok_or("window_dimensions are unset")?,
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let mut builder = GameBuilder::new();
builder.window_title("Awesome Builder");
builder.window_dimensions(800, 600);
let game = builder.build();
assert_eq!(
game.expect("build success"),
Game {
window: Window {
title: "Awesome Builder".into(),
dimensions: (800, 600)
}
}
);
}
#[test]
fn test_1() {
let game2 = GameBuilder::new()
.window_title("Busy Builder")
.window_dimensions(1234, 123)
.build();
assert_eq!(
game2.expect("build success"),
Game {
window: Window {
title: "Busy Builder".into(),
dimensions: (1234, 123),
}
}
)
}
}

Resources