Is there _any_ way to return a non-Clone struct containing a reference?

I think this isn't possible but struggled for long enough that I wanted to confirm.

I'm trying to return a Workspace struct, which contains references. I initially tried:

use cargo::core::Workspace;
use cargo::util::config::Config;
use std::path::Path;

fn workspace<'a>() -> Workspace<'a> {
    let config = Config::default().unwrap();
    let path = Path::new("/Users/maximilian/workspace/rust-test/Cargo.toml");
    Workspace::new(path, &config).unwrap()
}

fn main() {
    dbg!(workspace());
}

This raises:

error[E0515]: cannot return value referencing local variable `config`
  --> src/main.rs:13:5
   |
13 |     Workspace::new(path, &config).unwrap()
   |     ^^^^^^^^^^^^^^^^^^^^^-------^^^^^^^^^^
   |     |                    |
   |     |                    `config` is borrowed here
   |     returns a value referencing data owned by the current function

error: aborting due to previous error

For more information about this error, try `rustc --explain E0515`.
error: Could not compile `rust-test`.

To learn more, run the command again with --verbose.
[Finished running. Exit status: 101]

I think this is because:

  • the workspace function is returning a Workspace struct, which contains references created in the Config::new call
  • and a Workspace isn't Clone-able so it can't move the value into the calling function.

Is that correct?

One way around this is to have another function which creates the Config struct, so it's owned by the main function:


use cargo::util::config::Config;
use std::path::Path;

// new
fn c() -> Config {
    Config::default().unwrap()
}

// fn workspace<'a>() -> Workspace<'a> {
fn workspace<'a>(config: &'a Config) -> Workspace<'a> {
    // let config = Config::default().unwrap();
    let path = Path::new("/Users/maximilian/workspace/rust-test/Cargo.toml");
    Workspace::new(path, &config).unwrap()
}

fn main() {
    // dbg!(workspace());
    dbg!(workspace(&c()));
}

Questions I'd appreciate help with:

  • Is there any way to retain the structure of the original example, so that main doesn't have to pass around Config objects? I'm happy to use any combination of Box / Rc etc!
  • In the working second version, a function can return a locally created Config struct, despite it not implementing Clone, while the first version can't return a Workspace.
    • Is there a generalizable principle for what Structs locally created can be returned? They seem to implement the same Traits.
    • Is the difference that Workspace contains references and so has an explicit lifetime?

Thank you!

You can't return &config from workspace(), because config goes out of scope when workspace returns. When that happens &config becomes a dangling reference.

lazy_static!{
    pub static ref CONFIG: Config = Config::new().expect("Error initializing config");
}

is how I solved the problem. I'm interested in learning if there's a better way.

See Rental. (Also owning_ref and something else I forget, but first most likely for your case.)

1 Like

You can't return &config from workspace(), because config goes out of scope when workspace returns.

Maybe this is elementary, but in the second example, why can you return the config object?

Is it because you can return an struct which doesn't implement Clone as long as it doesn't contain references?

FYI the lazy_static approach doesn't work in this specific case, because Config doesn't implement Sync, but that's a separate issue. Thanks for the suggestion.

For the longer explanation:

You keep mentioning Clone, but Clone has no bearing on what you can do within the language. (objects are never implicitly cloned; you must call the clone method)

The function is returning the object by value. If you tried to return &Config, it would fail. (unless the &Config was a reference to something that outlives the function call; like one of the input arguments, or a static)

3 Likes