GithubHelp home page GithubHelp logo

Comments (5)

SergioBenitez avatar SergioBenitez commented on May 18, 2024 2

While explicitly cloning the state might seem like a good theoretical solution, in practice, the state could originate from a third-party library beyond the developer's scope, and the utilized state may lack implementation for the Clone and/or Copy trait. In such cases, cloning is not feasible.

This would mean that the axum example wouldn't work at all. It would simply fail to compile. This seems antithetic to the proposition of finesse.

Now, cloning works, but beginners to Rust and/or Rocket may not be aware that they need to dereference the state first before cloning, as demonstrated below:

You can also do client.inner().clone(), which is arguably simpler and more explicit.

Following the approach adopted by Axum, implicitly cloning the state would be preferable if the state is inherently cloneable to begin with. Therefore, I would like to propose the following solution:

The approach you're suggesting would mean that only cloneable state can be shared, and that every time you try to access said state you must pay the price to clone. Axum likely cannot expose direct references to share state due to internal design constraints, but Rocket can and doesn't need to fallback to cloning. Rocket gives you a direct reference - this is superior in every way except perhaps the need to call .clone() when that's what you actually want. Though even that can be refuted as running counter to the notion of zero-cost abstractions.

If you really want this abstraction, it's fairly straightforward to implement it yourself, actually:

use rocket::{Rocket, Ignite, State};
use rocket::outcome::{try_outcome, Outcome};
use rocket::request::{self, Request, FromRequest};

#[rocket::async_trait]
impl<'r, T: Send + Sync + Clone + 'static> FromRequest<'r> for Cloned<T> {
    type Error = ();

    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        let state = try_outcome!(req.guard::<&State<T>>().await);
        Outcome::Success(Cloned(state.inner().clone()))
    }
}

You can even make it a Sentinel:

impl<T: Send + Sync + 'static> Sentinel for Cloned<T> {
    fn abort(rocket: &Rocket<Ignite>) -> bool {
        <&State<T> as Sentinel>::abort(rocket)
    }
}

Then you can do just as you wished:

async fn generate_content(Cloned(mut client): Cloned<Client>) -> String {
    //...snip...
}

from rocket.

SergioBenitez avatar SergioBenitez commented on May 18, 2024

Instead of creating a brand new client in the handler, you could simply clone the existing reference you get from State. This would be equivalent to what Axum does, except doing it, explicitly, instead of having it be hidden from you. Rocket does not hide potentially expensive operations from you.

Were you aware aware of this behavior from Axum? If so, what aside from the above, would you propose? And if not, does my response above quell the request?

from rocket.

wiseaidev avatar wiseaidev commented on May 18, 2024

Rocket does not hide potentially expensive operations from you.

Nice! I wasn't aware of that until now; I just learned it the hard way.

Now, cloning works, but beginners to Rust and/or Rocket may not be aware that they need to dereference the state first before cloning, as demonstrated below:

async fn generate_content(client: &State<Client>, input_text: String) -> String {
    match (*client).clone().generate_content(&input_text).await {
        Ok(response) => response,
        Err(error) => error.to_string(),
    }
}

While explicitly cloning the state might seem like a good theoretical solution, in practice, the state could originate from a third-party library beyond the developer's scope, and the utilized state may lack implementation for the Clone and/or Copy trait. In such cases, cloning is not feasible.

Following the approach adopted by Axum, implicitly cloning the state would be preferable if the state is inherently cloneable to begin with. Therefore, I would like to propose the following solution:

// Auto implement the `FromRequest` trait at compile time?
async fn generate_content(client: &mut State<Client>, input_text: String) -> String {
    //...snip...
}

// or

async fn generate_content(State(mut client): &State<Client>, input_text: String) -> String {
    //...snip...
}

If the user indicates that client is mutable in the function argument, then allow borrowing as mutable or implicitly cloning the state.

Best

from rocket.

SergioBenitez avatar SergioBenitez commented on May 18, 2024

All that being said, none of this has much to do with mutability. Axum (or anything else in Rust) doesn't (and can't) magically let you share mutable state across threads. It's simply forbidden to share mutable state. You can, however, share state immutably that offers interior mutability, which effectively means using unsafe to work-around this restriction. This is what types like Mutex<T> do and libraries like dashmap offer. But it's not what axum does.

The reason your axum example "works" is because it's cloning Client, and you're getting an owned handle to the cloned object which you bind mutably. It's not doing anything interesting to allow you to share state mutably.

from rocket.

wiseaidev avatar wiseaidev commented on May 18, 2024

Rocket gives you a direct reference - this is superior in every way except perhaps the need to call .clone() when that's what you actually want. Though even that can be refuted as running counter to the notion of zero-cost abstractions.

That's actually a good point, and it aligns with the idea of keeping things efficient and giving the developer the ultimate power over things. Additionally, the example you provided about implementing the FromRequest trait, it's kind of what I mentioned about with the "Auto implement the FromRequest trait at compile time?" comment in the code, but automatically generate this implementation. I'm not sure how hard it would be to implement it, but it's definitely something we could improve on.

Now, despite my skill issues and even though I'm still figuring things out with Rocket, there's something that a lot of backend developers might be wondering about. Maybe we could update the docs about state to clear things up a bit. In any case, I just wanted to bring this up to your attention that sates mutability is something that a lot might be wondering about.

That's pretty much it, feel free to close the issue if you think it is already sorted out.

Best

from rocket.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.