Alternative to cloning string

Hello,

I am trying to build a simple HTTP server with warp. This is what I have so far:

#![feature(fs_try_exists)]

use std::fs;

use clap::{arg, command};
use warp::Filter;
use warp::http::StatusCode;

// simple state file: if content equals "1" return true, otherwise false
fn read_state(state_file: &str) -> bool {
    match fs::try_exists(state_file).expect("Could not check if file exists") {
        true =>
            match fs::read_to_string(state_file).expect("Could not read state from file").as_str() {
                "1" => true,
                _ => false,
            }
        false => {
            // default to false
            false
        }
    }
}

#[tokio::main]
async fn main() {
    let matches = command!()
        .arg(
            arg!(
                -s --state <FILE> "Sets a custom state file"
            )
            .default_value("state.dat")
            .required(false)
        )
        .get_matches();

    let state_file = matches.get_one::<String>("state")
        .expect("Could not parse cli arg");

    println!("Storing state in: {state_file}");

    let get_state_route = warp::path!("state")
        .map({
            let state_file = state_file.clone();
            move || {
                let state = read_state(&state_file);
                if state { StatusCode::IM_A_TEAPOT } else { StatusCode::OK }
            }
        });

    warp::serve(get_state_route)
        .run(([127, 0, 0, 1], 3030))
        .await;
}

Is it possible to refer to the state_file string without cloning it, e.g. what is done by:

let state_file = state_file.clone();

?

Regards,
Stefan

Well, given that you only do it one time I guess leaking the string would be a possibility:

    ...

    let state_file = matches.get_one::<String>("state")
        .expect("Could not parse cli arg");

    let state_file: &'static str = Box::leak(state_file.into_boxed_str());

    ...    

Why don't you want to clone the string in the first place? Cloning a file name is hardly going to be the bottleneck of your web server.

1 Like

Try changing get_one to remove_one, which gives you an owned String that you can move into the closure:

    let state_file = matches.remove_one::<String>("state")
        .expect("Could not parse cli arg");

    let get_state_route = warp::path!("state")
        .map({
            move || {
                let state = read_state(&state_file);
                if state { StatusCode::IM_A_TEAPOT } else { StatusCode::OK }
            }
        });
2 Likes

With the proposed code I get the following error:

error[E0507]: cannot move out of `*state_file` which is behind a shared reference
    --> src/main.rs:39:46
     |
39   |     let state_file: &'static str = Box::leak(state_file.into_boxed_str());
     |                                              ^^^^^^^^^^ ---------------- `*state_file` moved due to this method call
     |                                              |
     |                                              move occurs because `*state_file` has type `std::string::String`, which does not implement the `Copy` trait
     |
note: `std::string::String::into_boxed_str` takes ownership of the receiver `self`, which moves `*state_file`
    --> ~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/string.rs:1943:27
     |
1943 |     pub fn into_boxed_str(self) -> Box<str> {
     |                           ^^^^
help: you can `clone` the value and consume it, but this might not be your desired behavior
     |
39   |     let state_file: &'static str = Box::leak(<std::string::String as Clone>::clone(&state_file).into_boxed_str());
     |                                              +++++++++++++++++++++++++++++++++++++++          +

Should I use the solution which the compiler gives and do an additional clone or is there another way which does not involve cloning?

In real code I might have multiple routes depending on that string. As well the string data might be larger than just a file path. That is why I wanted to avoid cloning.

Rather than cloning, I'd do what @mbrubeck suggested and use remove_one rather than get_one.

Thanks @mbrubeck and @jofas! But now when I want to use the state_file string from multiple routes this wont work, e.g.

#[tokio::main]
async fn main() {
    let mut matches = command!()
        .arg(
            arg!(
                -s --state <FILE> "Sets a custom state file"
            )
            .default_value("state.dat")
            .required(false)
        )
        .get_matches();

    let state_file = matches.remove_one::<String>("state")
        .expect("Could not parse cli arg");

    let get_state_route = warp::path!("state")
        .map({
            move || {
                let state = read_state(&state_file);
                if state { StatusCode::IM_A_TEAPOT } else { StatusCode::OK }
            }
        });

    let get_state_route_2 = warp::path!("state2")
        .map({
            move || {
                let state = read_state(&state_file);
                if state { StatusCode::IM_A_TEAPOT } else { StatusCode::OK }
            }
        });

    warp::serve(get_state_route)
        .run(([127, 0, 0, 1], 3030))
        .await;
}

The compiler gives:

error[E0382]: use of moved value: `state_file`
  --> src/main.rs:57:13
   |
37 |     let state_file = matches.remove_one::<String>("state")
   |         ---------- move occurs because `state_file` has type `std::string::String`, which does not implement the `Copy` trait
...
48 |             move || {
   |             ------- value moved into closure here
49 |                 let state = read_state(&state_file);
   |                                         ---------- variable moved due to use in closure
...
57 |             move || {
   |             ^^^^^^^ value used here after move
58 |                 let state = read_state(&state_file);
   |                                         ---------- use occurs due to use in closure

One option would be to go with matches.remove_one and Arc<T> and clone this instead, e.g.

let state_file = matches.remove_one::<String>("state")
    .expect("Could not parse cli arg");

let state_file = Arc::new(state_file);

let get_state_route = warp::path!("state")
    .map({
        let state_file = state_file.clone();
        move || {
            let state = read_state(&state_file);
            if state { StatusCode::IM_A_TEAPOT } else { StatusCode::OK }
        }
    });

Any further ideas?

That seems like a good idea, and you could avoid one clone by calling get_one as you were doing before to get a string ref, and then use into to clone it into an Arc<str>.

let state_file = matches.get_one...
let state_file: Arc<str> = state_file.into();

Edit: After looking at what types are returned by get_one and remove_one in clap, I'm still not certain that you avoid a clone by doing what I suggest above. But I think that is likely and doing it this way doesn't have any drawbacks.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.