Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Bongo Bill
Jan 17, 2012

ADTs are a good enough feature that I'd be willing to put up with a vastly worse language than Rust in order to be able to use them.

Adbot
ADBOT LOVES YOU

gonadic io
Feb 16, 2011

>>=
I did for years, it's called haskell

Ranzear
Jul 25, 2013

prom candy posted:

I am using Typescript, it was the language that initially made me realize that compilers can tell me when I'm being an idiot and it made me completely fall out of love with Ruby.

Then again there's also Go...

gonadic io
Feb 16, 2011

>>=
If you want to see what it's like when you have a compiler but forego half the benefits of it

Ranzear
Jul 25, 2013

Ranzear posted:

Rust compiler: "This is wrong because you're stupid."
Golang compiler: "This is wrong because I'm stupid."

I still love this post. There really is a difference between a strict compiler and an actually helpful compiler.

repiv
Aug 13, 2009


seems like more drama has been brewing, one of the four members of the core team just resigned outright

https://twitter.com/jntrnr/status/1662229886386442242

Ranzear
Jul 25, 2013

PYF error handling crate? I've been through a few now and picked up Anyhow from the wgpu tutorial.

I feel like this is how Result should work to begin with, just wrapping the return type because why should one fiddle with error types on trivial cases while <T, E> is still a separate extended option for more explicit handling. It still supports expect() while adding ensure(), an alternative to assert() that bubbles an error instead of plain-rear end panicking, something I've desperately needed as a final catch-all for accepting client inputs. I also like context() which is more about where things hosed up rather than what specific thing and is way more idiomatic than ThisError's attribute spam. bail() is funny for matching exactly how I used to make do ... while(false) blocks to 'bail out of' with a break in PHP.

All these ?'s make my code seem indecisive though...

Ranzear fucked around with this message at 23:14 on May 28, 2023

big black turnout
Jan 13, 2009



Fallen Rib
The general vibe a couple years ago was thiserror for libraries, anyhow for applications

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

big black turnout posted:

The general vibe a couple years ago was thiserror for libraries, anyhow for applications

This is the standard practice for our stuff.

gonadic io
Feb 16, 2011

>>=
I believe miette has found some success as an anyhow successor but we still use thiserror and anyhow too.

Note ranzear that the reason people say use thiserror for libs, despite how is absolutely is more cumbersome, is that anyhow errors are all user displayable strings, thiserror's enums are for when you're catching and recovering inside code so need to actually see which error happened. No substring searching here tyvm (cough go cough).

https://docs.rs/miette/latest/miette/

gonadic io fucked around with this message at 01:40 on May 29, 2023

Vilgefartz
Apr 29, 2013

Good ideas 4 free
Fun Shoe
Hi all, I am trying to write a basic project with rust and the borrow checker is humbling me constantly.

I'm starting to get my head around the ownership and mutable references rules, and i have a question about structuring some sort of getter.

I have a basic update function that is trying to get a mutable reference to a vector inside a struct.

The struct layout looks like this:

code:
GameState {
	Areas: Vec<{
		decks: Vec<Deck>,
		slots: Vec<Slot>
	}>
}
Which is being referenced in an update function from some other struct like this

code:
    fn update(&mut self, state: &mut GameState) -> GameResult {

        for p in &state.players {
            let area = get_area_by_id(&mut state.areas, *p)?; // Mut ref to areas vec
            let deck = get_deck(&mut area.decks, 0)?; // Mut ref to single area instance

            for i in 0..area.slots.len() {
                let slot = get_slot(&mut area.slots, i)?;
                if slot.is_empty() {
                    move_card(deck, slot, 0);
                }
            }
        }
    }

Now i want to get mutable references to the slots vector and the decks vector.

This is easy enough if i have some external function that takes a mutable reference to the areas.slots vector.

But i was wondering, if there a way to make this a method of the Areas struct, without having to take a mutable reference to the areas struct?


EDIT: Ok sorted. Writing this out seemed to fix it. Just had to use an associated function (Areas::get_slot etc...). Though i am interested in if anyone thinks this is a bad approach...

Vilgefartz fucked around with this message at 07:27 on Jun 10, 2023

mondomole
Jun 16, 2023

Vilgefartz posted:

Though i am interested in if anyone thinks this is a bad approach...

One thought: why do you need the mutable references to deck and slots to be exposed? I think giving out mutable references to internal state is generally a bit of a code smell. In doing this, you are letting downstream users (even if it's yourself) mess around with your state arbitrarily. It may be better to implement some methods on top of Areas to do whatever it is that you were going to do with those mut refs.

gonadic io
Feb 16, 2011

>>=

Vilgefartz posted:



EDIT: Ok sorted. Writing this out seemed to fix it. Just had to use an associated function (Areas::get_slot etc...). Though i am interested in if anyone thinks this is a bad approach...

I'm a bit confused at this part, associated functions or methods are just syntactic sugar.
code:
Areas::get_slot(&mut foo)
is equivalent to
code:
foo.get_slot()
where foo is mut and an Area. You don't even have to do
code:
(&mut foo).get_slot()
as rust will insert the refs for you where needed.

mondomole
Jun 16, 2023

gonadic io posted:

I'm a bit confused at this part, associated functions or methods are just syntactic sugar.

This isn't entirely true. One difference is how splitting borrows are inferred. For example:

code:
struct Foo {
    a: u8,
    b: u8,
}

impl Foo {
    fn a_mut(&mut self) -> &mut u8 {
        &mut self.a
    }
    
    fn b_mut(&mut self) -> &mut u8 {
        &mut self.b
    }
}
This example compiles and prints correctly:

code:
fn main() {
    let mut foo = Foo { a: 1, b: 2 };
    
    let a = &mut foo.a;
    let b = &mut foo.b;
    
    println!("{a:?}, {b:?}");
}
This example fails due to a borrow checker violation:

code:
fn main() {
    let mut foo = Foo { a: 1, b: 2 };
       
    let a = foo.a_mut();
    let b = foo.b_mut();
    
    println!("{a:?}, {b:?}");
}

gonadic io
Feb 16, 2011

>>=
I mean sure, you're absolutely right with a method call vs no call at all, but I mean that your last snippet acts exactly the same as this one:
code:
fn main() {
    let mut foo = Foo { a: 1, b: 2 };
       
    let a = Foo::a_mut(&mut foo);
    let b = Foo::b_mut(&mut foo);
    
    println!("{a:?}, {b:?}");
}

mondomole
Jun 16, 2023

gonadic io posted:

I mean sure, you're absolutely right with a method call vs no call at all, but I mean that your last snippet acts exactly the same as this

Oh you're right, my bad. I agree between those two there can't be any difference.

Vilgefartz posted:

code:
                let slot = get_slot(&mut area.slots, i)?;
EDIT: Ok sorted. Writing this out seemed to fix it. Just had to use an associated function (Areas::get_slot etc...). Though i am interested in if anyone thinks this is a bad approach...

Judging by this snippet, I would guess that before the code was something like area.get_slot(i) and area.get_deck(i) leading to multiple borrows of area as opposed to directly borrowing &mut area.slots and &mut area.deck in the current posted version.

Vilgefartz
Apr 29, 2013

Good ideas 4 free
Fun Shoe

mondomole posted:

Oh you're right, my bad. I agree between those two there can't be any difference.

Judging by this snippet, I would guess that before the code was something like area.get_slot(i) and area.get_deck(i) leading to multiple borrows of area as opposed to directly borrowing &mut area.slots and &mut area.deck in the current posted version.

Yeah that's the case. I'm iterating over a vector of Area's, and i want to borrow invidual Slots and Decks mutably, which i can't at the same time using a method on Area

mondomole posted:

One thought: why do you need the mutable references to deck and slots to be exposed? I think giving out mutable references to internal state is generally a bit of a code smell. In doing this, you are letting downstream users (even if it's yourself) mess around with your state arbitrarily. It may be better to implement some methods on top of Areas to do whatever it is that you were going to do with those mut refs.

I'm actually not really sure how to structure this appropriately, it's very much a learning exercise. I'm just kind of hashing things out. Right now i'm getting mutable references because i'm passing things around to functions that need mutable references to operate on. This is what the updated function looks like:

code:
    fn update(&mut self, state: &mut GameState) -> GameResult {
        // Fill all monster slots from monster deck
        // Fill all player slots from player deck

        let area = GameState::get_area_by_owner_id(&mut state.areas, 5)?;
        let deck = Area::get_deck(&mut area.decks, 0)?;
        fill_slots_from_deck(deck, &mut area.slots)?;

        for p in &state.players {
            let area = GameState::get_area_by_owner_id(&mut state.areas, p.id)?;
            let deck = Area::get_deck(&mut area.decks, 0)?;
            fill_slots_from_deck(deck, &mut area.slots)?;
        }

        self.status = GamePhaseStatus::Finished;

        Ok(())
    }

mondomole
Jun 16, 2023

Vilgefartz posted:

Right now i'm getting mutable references because i'm passing things around to functions that need mutable references to operate on.

I think two refactoring to consider would be:

1. Pretend state has only private members and write the mutations as functions of external parameters only. This avoids situations where you would have multiple private references floating around publicly. So in your case it would involve moving the update function to GameState. This doesn’t solve this exact issue because you would still be taking multiple mut references to self internally. But if you also treat the objects held inside as only having private members with public accessor methods, you avoid the issue because you would never let the outside state get a mut ref to an internal nested field.

2. One pattern to force the behavior I’m describing above is to move in GameState to the update function, destructure it temporarily, and return the updated one. This “pipeline” trick is useful in a lot of situations. Roughly speaking:

code:
fn update(mut state: GameState) -> GameState {
    let GameState { foo, bar } = state;
    /// Do stuff with foo and bar here. 
    /// “state” doesn’t exist so by construction you
    /// are forced to treat the internal state as separate
    /// objects. You accomplish the same thing if you
    /// use the convention that state methods never 
    /// directly take mut refs to foo and bar. 

    GameState { foo, bar }
}

Love Stole the Day
Nov 4, 2012
Please give me free quality professional advice so I can be a baby about it and insult you
Looking for examples of good abstractions and separation of concerns to use for web dev use cases (e.g. CRUD). Preferably involving `actix_web`.

Only good example from arewewebyet.com is this one: https://github.dev/LemmyNet/lemmy however their abstractions (such at in api_routes_http.rs) don't seem to be well enough organized that I'd want to replicate that in my own project.

Am feeling it out as I go, but it'd be helpful if there was a good, clean demonstration from which to learn.

gonadic io
Feb 16, 2011

>>=
Don't use actix web if you can help it imo
It's pretty abandoned. We still have the legacy of actix itself at work and it's painful every day.

Love Stole the Day
Nov 4, 2012
Please give me free quality professional advice so I can be a baby about it and insult you

gonadic io posted:

Don't use actix web if you can help it imo
It's pretty abandoned. We still have the legacy of actix itself at work and it's painful every day.

I know tokio is very popular, but all of the back-end web frameworks which leverage it seem to be immature, 0.x versions. What's the mature, production-ready, back-end web framework to use, if not actix_web?

mondomole
Jun 16, 2023

gonadic io posted:

Don't use actix web if you can help it imo

Love Stole the Day posted:

Only good example from arewewebyet.com is this one: https://github.dev/LemmyNet/lemmy however their abstractions (such at in api_routes_http.rs) don't seem to be well enough organized that I'd want to replicate that in my own project.

I second trying to minimize the surface area of actix-web. I don't think it's an awful library but it feels super opinionated about state handling in a way that seemed simultaneously too magical and too restrictive. Not sure if this is an option for you, but we settled on serving static files and redirects from actix web using a similar pattern as lemmy is doing and then doing all data endpoints with gRPC via tonic-web. That's worked out pretty well for us since the gRPC piece ends up looking like any other backend instead of being a one-off for the frontend.

gonadic io
Feb 16, 2011

>>=
We've switched to Axum at work and while I haven't used it myself I haven't heard much in the way of complaints

crazypenguin
Mar 9, 2005
nothing witty here, move along
Axum definitely appears to be the default choice for building web service stuff in Rust at this time.

If you're pretty excited about Rust and kinda new still, I definitely recommend just casually looking over the projects in the tokio-rs org on github: https://github.com/tokio-rs

What's there (vs elsewhere) is of course a messy social process, so it's not like a one-stop shop, but they're all at least interesting, and use other interesting stuff.

kujeger
Feb 19, 2004

OH YES HA HA
Yeah axum is quite nice, we've found it a lot more ergonomic to work with than actix-web was. We even rewrote actix stuff we had into axum so we could standardize the stack.

Love Stole the Day
Nov 4, 2012
Please give me free quality professional advice so I can be a baby about it and insult you
Okay, axum it is. Sounds good!

What about the abstractions you all use to organize everything for your web services? Are there any open source examples worth following?

Love Stole the Day
Nov 4, 2012
Please give me free quality professional advice so I can be a baby about it and insult you
Here's what I came up with. Please flame me, starting with the lack of unit tests and functional tests: https://github.com/wanderrful/myaxumserv

Readme has a sequence diagram to show the separation of concerns, along with sample curl requests to run, if you build and run it locally

mondomole
Jun 16, 2023

Love Stole the Day posted:

Here's what I came up with. Please flame me, starting with the lack of unit tests and functional tests: https://github.com/wanderrful/myaxumserv

Readme has a sequence diagram to show the separation of concerns, along with sample curl requests to run, if you build and run it locally

Using From to map between state and requests is an interesting idea but in my opinion it makes the code much less readable. Looking at managers/user.rs, it's hard to understand the logic at a glance since you need context from the conversion functions. For example, it's not immediately obviously whether the ID is generated by your code at all, or whether you're relying on something like an auto-incremented ID in the storage layer. I think the code will be more clear if you move that logic into the manager and make it more explicit what is going on:

code:
/// Paraphrasing a bit here in create_user

let user_model = UserModel {
    username: payload.username.clone(),
    id: self.generate_id(),
};
self.write(user_model);
Similarly in list_users, as you augment the types of information you may want to return, it becomes less and less intuitive where the separation of responsibility is. For example: if you want to add a query begin timestamp, or some instrumentation on how long a query took, or if you want to add a tracing ID to log all correlated queries from a given user-initiated RPC call, or whatever, it starts to become a dance between From and the manager. I think it would be clearer to move this all into list_users more explicitly.

I admit this may be a bit of a hot take though. I've always pushed back against DRY.

Vilgefartz
Apr 29, 2013

Good ideas 4 free
Fun Shoe

mondomole posted:

I think two refactoring to consider would be:

1. Pretend state has only private members and write the mutations as functions of external parameters only. This avoids situations where you would have multiple private references floating around publicly. So in your case it would involve moving the update function to GameState. This doesn’t solve this exact issue because you would still be taking multiple mut references to self internally. But if you also treat the objects held inside as only having private members with public accessor methods, you avoid the issue because you would never let the outside state get a mut ref to an internal nested field.

2. One pattern to force the behavior I’m describing above is to move in GameState to the update function, destructure it temporarily, and return the updated one. This “pipeline” trick is useful in a lot of situations. Roughly speaking:

code:
fn update(mut state: GameState) -> GameState {
    let GameState { foo, bar } = state;
    /// Do stuff with foo and bar here. 
    /// “state” doesn’t exist so by construction you
    /// are forced to treat the internal state as separate
    /// objects. You accomplish the same thing if you
    /// use the convention that state methods never 
    /// directly take mut refs to foo and bar. 

    GameState { foo, bar }
}

My lizard brain is having an amazing amount of trouble parsing this.

Right now i have things set up similar to this:

code:

Game {
	state: GameState,
	phases: Vec<Box<dyn GamePhase>>,
}

GameState {
	phase_idx: usize
	areas: Vec<{
		slot: Vec<Slot>,
		decks: Vec<Deck>
	}>
}

impl Game {
	fn update(&mut self)  {
		self.phases[self.state.phase_idx].update(&mut self.state)
	}
}

struct TestPhase {}
impl GamePhase for TestPhase {
	fn update(&mut self, state: &mut GameState) { ...etc }
}

So each update loop, it calls update on the game, which passes its state child across to its vector of phases child, and the current phase runs its update function on the gamestate.

So instead should i call GameState.update() with the phase as an argument so the state is only mutated internally? Sorry if i've misunderstood.

code:
GameState.update(&mut self, phase: Box<dyn GamePhase>) {
	phase.update(self)
}
Edit. I am wracking my brain to understand this. Is it kind of what this article is advocating? https://www.tedinski.com/2019/04/30/encapsulating-state.html

Vilgefartz fucked around with this message at 11:59 on Jun 19, 2023

mondomole
Jun 16, 2023

I misunderstood what you were doing so I'm deleting my extremely convoluted and incorrect explanation :)

The part you've shown there seems reasonable. The issue seems to be more so in GameState itself when you are trying to mutate different sub-components separately:

code:
struct GameState {
    foo: Foo,
    bar: Bar,
}

/// My impression is that you were essentially trying to do
/// this earlier before rewriting to access state.foo, state.bar
/// directly.
impl GameState {
    fn foo_mut(&mut self) -> &mut Foo;
    fn bar_mut(&mut self) -> &mut Bar;
}
The above has problems if you call both foo_mut and bar_mut and use the return values (as you discovered). My proposal was one of the following:

1)

code:
impl GameState {
    /// Don't allow direct access to internal state
    // fn foo_mut(&mut self) -> &mut Foo;

    fn do_something_with_foo(&mut self, params: &Baz);

    /// Similar for bar
}
2)

code:
impl GameState {
    /// Intentionally move these pieces out so that the caller
    /// can mutate the internal state and then stitch back
    /// together.
    fn into_foobar(self) -> (Foo, Bar) {
        (self.foo, self.bar)
    }
    
    fn from_foobar(foo: Foo, bar: Bar);
}

let (mut foo, mut bar) = state.into_foobar();
// ...
let state = GameState::from_foobar(foo, bar);
For 2, instead of consuming self you can also partially move out with Option, or use unsafe and MaybeUninit if Option is too expensive for your use case:

code:
impl GameState {
    /// Moves out foo and bar so you can mutate them separately.
    /// Doesn't consume self.
    /// Safe because self.foo, self.bar are now None while
    /// while you're arbitrarily mutating the returned Foo, Bar.
    ///
    /// After the arbitrary mutations are done, move the Foo and Bar
    /// back inside GameState.
    fn take_foobar(&mut self) -> (Option<Foo>, Option<Bar>) {
        (self.foo.take(), self.bar.take())
    }
}

let (mut foo, mut bar) = state.into_foobar();
// ...
let state = GameState::from_foobar(foo, bar);

mondomole fucked around with this message at 14:01 on Jun 19, 2023

Vilgefartz
Apr 29, 2013

Good ideas 4 free
Fun Shoe
That's my bad i probably should've included the rest of the code. I really appreciate you taking the time to address my questions.

So in the case i want to mutate individual child properties in the parent...

I could either write a function on the actual gameState that does the bespoke thing i want, or destructure the children so ownership can be taken for each property individually, instead of writing some external function that may not always actually make sense as an external function. That makes sense..

Love Stole the Day
Nov 4, 2012
Please give me free quality professional advice so I can be a baby about it and insult you

Love Stole the Day posted:

Here's what I came up with. Please flame me, starting with the lack of unit tests and functional tests: https://github.com/wanderrful/myaxumserv

Readme has a sequence diagram to show the separation of concerns, along with sample curl requests to run, if you build and run it locally

FYI I've updated this with dependency injection and I like how it's starting to come together. This is kind of snowballing into a fully-featured thing. Just need to add opentelemetry stuff, configuration stuff, testing stuff, some downstream calls, API versioning, maybe a Swagger codegen thing, etc... and it'll be almost like an enterprise-level service!

Haven't been able to find any demo CRUD web service projects on Github using this "tech stack", so I like to think I'm doing something useful for the community here.

prom candy
Dec 16, 2005

Only I may dance

Love Stole the Day posted:

FYI I've updated this with dependency injection and I like how it's starting to come together. This is kind of snowballing into a fully-featured thing. Just need to add opentelemetry stuff, configuration stuff, testing stuff, some downstream calls, API versioning, maybe a Swagger codegen thing, etc... and it'll be almost like an enterprise-level service!

Haven't been able to find any demo CRUD web service projects on Github using this "tech stack", so I like to think I'm doing something useful for the community here.

This is cool! Not a Rust-specific critique but organizationally it kinda feels like there aren't clear boundaries between the responsibilities of Resources and Managers. If I was building a framework from scratch I'd probably omit Managers but provide examples of how to call encapsulated service-layer code if necessary. Just my two cents.

Love Stole the Day
Nov 4, 2012
Please give me free quality professional advice so I can be a baby about it and insult you

prom candy posted:

This is cool! Not a Rust-specific critique but organizationally it kinda feels like there aren't clear boundaries between the responsibilities of Resources and Managers. If I was building a framework from scratch I'd probably omit Managers but provide examples of how to call encapsulated service-layer code if necessary. Just my two cents.

Yeah I agree with you as it is right now: the Manager takes the request DTO and returns the response DTO, whereas the Resource is only validating the request and dealing with the outcome of the operation.

So, the Manager is responsible for orchestrating everything, and the Resource is responsible for validation and assembling the final response (i.e. if the manager fails for some reason, then the Resource would be mapping the error to whatever error response and HTTP error code is needed).

There is probably a better pattern I should use here (edit: maybe wrap the Manager with a "before Aspect" and an "after Aspect"? But I think Aspects for side-effects, not validation and exception->error mapping). As-is, the code doesn't have validation or error handling and so I agree with you that the Resource doesn't have much reason to exist right now.

I feel like if we move the "validation" and "exception -> error response" responsibilities into the Manager, then it will have too much stuff to do. I think the Manager should just focus on the operation and nothing else.

Controller-Service-Repository/Delegate are the analogous concepts in Spring Boot (Java) world to which I'm more accustomed.

Love Stole the Day fucked around with this message at 22:55 on Jun 23, 2023

prom candy
Dec 16, 2005

Only I may dance

Love Stole the Day posted:

Yeah I agree with you as it is right now: the Manager takes the request DTO and returns the response DTO, whereas the Resource is only validating the request and dealing with the outcome of the operation.

So, the Manager is responsible for orchestrating everything, and the Resource is responsible for validation and assembling the final response (i.e. if the manager fails for some reason, then the Resource would be mapping the error to whatever error response and HTTP error code is needed).

There is probably a better pattern I should use here (edit: maybe wrap the Manager with a "before Aspect" and an "after Aspect"? But I think Aspects for side-effects, not validation and exception->error mapping). As-is, the code doesn't have validation or error handling and so I agree with you that the Resource doesn't have much reason to exist right now.

I feel like if we move the "validation" and "exception -> error response" responsibilities into the Manager, then it will have too much stuff to do. I think the Manager should just focus on the operation and nothing else.

Controller-Service-Repository/Delegate are the analogous concepts in Spring Boot (Java) world to which I'm more accustomed.

My thought is more that the Controller-type resource (actually Resource in this case) should be responsible for taking in a request and returning a response and anything beyond that is potentially overly prescriptive. A Manager pattern could be a recommendation ("Help, my Resources are doing too much!") but from my general experience working with web frameworks if I had to pick between one object or method that's doing a bit too much vs. two objects or methods that appear to be tag teaming the same simple task. That said I've been finding myself preferring longer functions in code in general lately and that may not be a majority opinion. A lot of the stuff that I took as gospel about programming from OOP gurus like short methods and SRP I've started to discover can actually just make it really hard to find where the actual behaviour you're looking for is.

Again just my opinion.

Love Stole the Day
Nov 4, 2012
Please give me free quality professional advice so I can be a baby about it and insult you

prom candy posted:

My thought is more that the Controller-type resource (actually Resource in this case) should be responsible for taking in a request and returning a response and anything beyond that is potentially overly prescriptive. A Manager pattern could be a recommendation ("Help, my Resources are doing too much!") but from my general experience working with web frameworks if I had to pick between one object or method that's doing a bit too much vs. two objects or methods that appear to be tag teaming the same simple task. That said I've been finding myself preferring longer functions in code in general lately and that may not be a majority opinion. A lot of the stuff that I took as gospel about programming from OOP gurus like short methods and SRP I've started to discover can actually just make it really hard to find where the actual behaviour you're looking for is.

Again just my opinion.

I agree with you about the Manager pattern. I should've put more thought into the naming of these concepts. Maybe I should just stick to the Controller-Service-Delegate/Repository naming convention for this separation of concerns.

What you describe as a Controller-type resource, I suppose, would be the `UserRouter`, because it has those async functions which do exactly that. Maybe I should turn that into some kind of Factory, but I'm not sure if that'd add any value because there is only one use case such a concept will ever need to support. `UserRouter::new()` looks like a Builder or Factory pattern, though. I should try refactoring it into a `tower_service::Service` type and see how that looks.

--

By the way, side question: what crate do you all recommend for configuration management? I can do it manually with `serde` to open and serialize a YAML or JSON file, of course, but I worry about the more general use cases an enterprise service would need and think it'd be bad to implement that from scratch for myself.

Hadlock
Nov 9, 2004

I have a crude 3d space thing written in rust where the user can navigate using WASD. It reads input, does some math and then draws to screen like any other GUI system, nothing exotic besides being rust

Is there some way I could setup integration tests where I can record input doing one lap, then replay it on application startup using a flag to enable this. Right now I'm doing this manually and it's already getting tedious.

I think if this were 1995 and I were John carmack I'd call this "recording a demo"?

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Presumably your game is using some sort of centralised input manager instead of directly querying keyboard state, yeah? That's the logical place to add this sort of functionality.

Basically when recording a demo, the input manager logs all the inputs every simulation frame. When playing back a demo, it returns inputs from the demo file instead of polling the input devices on each frame. The rest of your code doesn't need to care about whether it's a demo playback or not.

If your game simulation is independent of framerate then that's really all you need to do.

If the game simulation is framerate-dependent then you're probably in for a bit of a headache, since you'll need to store the frame timings in the demo file as well and use those instead of the real time when playing it back.

lifg
Dec 4, 2000
<this tag left blank>
Muldoon
What’s the best practice for storing simple enums in a database? I’ve been manually implementing Display and FromStr to make it easy to convert to and from an SQL text column, but is there a better way?

Adbot
ADBOT LOVES YOU

kujeger
Feb 19, 2004

OH YES HA HA
I don't know if it's best practice or whatnot, but we've been using the strum and strum_macros crates to easily derive Display for just that.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply