The trait `Deserialize<'_>` is not implemented for `&mut MyStruct`

Hi,
I want to read a config file (config.toml) into a struct.

I thought I can instantiate the struct in main() with:

let config = Config::default().load();

Meaning:
Instantiate struct with default values(None) and then load actual config values from file and "update" values in struct.

But in the load method I get the error:
"the trait Deserialize<'_> is not implemented for &mut Config"
for toml::from_slice(&bytes)

When I use load(mut self) -> Self {} instead of using load(&mut self){} it works.
The error in the editor is even more confusing

I'm not sure if I understand this correctly.
I understand that I'm not allowed to use a reference directly with serde. Is that right?

How could I achieve zero-copy deserialization? It's not a big issue with this config thing, but I'm curious...

use serde::Deserialize;
use std::net::IpAddr;
use std::process::exit;

#[derive(Default, Deserialize)]
pub struct Config {
    userdir: String,
    with_data: bool,
    #[serde(alias = "server")]
    pub srv: Server,
}

#[derive(Default, Deserialize)]
pub struct Server {
    host: String,
    host_url: Option<String>,
    domain: String,
    proxy_prefix: String,
    proxy_user: String,
}

impl Config {
    pub fn load(&mut self) {
        let bytes = std::fs::read("./config.toml").expect("oops");
        self = match toml::from_slice(&bytes) {
            Ok(c) => c,
            Err(_) => {
                eprintln!("Could not read config");
                exit(1);
            }
        };
    }

    pub fn is_ip(&self) -> bool {
        let ip: Option<IpAddr> = self.srv.host.parse().ok();
        ip.is_some()
    }
}

&mut self is short for self: &mut Self, which in this case is &mut Config. When you write self = match ..., you're trying to assign to a value of type &mut Config. This causes the compiler to infer you're trying to deserialize a &mut Config. You can't deserialize from toml to a &mut Config anything, because &mut Config is only a reference to a Config that exists somewhere else.

If you change the self = match ... to *self = match ... (* being the dereference operator), you're assigning to the value pointed to by the &mut reference, which has the type Config. With this change, the code will compile.

I think a zero-copy solution would look like something like this (possibly replacing &'a str with Cow<'a, str>)

#[derive(Default, Deserialize)]
pub struct Config<'a> {
    userdir: &'a str,
    with_data: bool,
    #[serde(alias = "server")]
    pub srv: Server<'a>,
}

#[derive(Default, Deserialize)]
pub struct Server<'a> {
    host: &'a str,
    host_url: Option<&'a str>,
    domain: &'a str,
    proxy_prefix: &'a str,
    proxy_user: &'a str,
}

impl<'a> Config<'a> {
    pub fn load(&mut self, mut bytes: &'a [u8]) {
        *self = match toml::from_slice(bytes) {
            Ok(c) => c,
            Err(_) => {
                eprintln!("Could not read config");
                exit(1);
            }
        };
    }
}

You can't read the file in load when doing zero-copy because the data would get dropped at the end of the function.

2 Likes

Thank you very much for your explanation and the example.
Dereferencing with * worked.

Which version would be normally preferred?

  1. Passing and returning an instance of self
    or
  2. &mut self and changing with *self
    I don't see a practical difference in the result?

I built a simple example which runs on the playground which seems to give a good compiler error for this problem.

#[derive(Debug)]
struct Config {
    field1: String,
    field2: String,
}   
fn main() {
    let mut config = Config {
        field1: "".to_string(),
        field2: "".to_string(),
    };
    change(&mut config);
    println!("field1: {:?}", config.field1);
    println!("field2: {:?}", config.field2);
}

fn change(config: &mut Config) {
    let field1 = String::from("value1");
    let field2 = String::from("value2");
    //-- without * (dereferencing) the compiler complains (below)
    //config = Config {
    *config = Config {
        field1,
        field2,
    }
}
//-- Compiling playground v0.0.1 (/playground)
//-- error[E0308]: mismatched types
//-- --> src/main.rs:24:14
//--    |
//-- 19 |   fn change(config: &mut Config) {
//-- |                     ----------- expected due to this parameter type
//-- ...
//-- 24 |       config = Config {
//--    |  ______________^
//-- 25 | |         field1,
//-- 26 | |         field2,
//-- 27 | |     }
//--    | |_____^ expected `&mut Config`, found struct `Config`
//--    |
//-- help: consider dereferencing here to assign to the mutably borrowed value
//--    |
//-- 24 |     *config = Config {
//--    |     +
//-- 
//-- For more information about this error, try `rustc --explain E0308`.
//-- error: could not compile `playground` due to previous error

Yes, changing a pointer itself is not the same thing as changing the value that it points to.

1, i.e., just return the config by value. Configs are usually not large enough to be worth trying to re-use buffers (which passing a mutable reference would allow you to do), and working with values is simpler.

(And if your config is so big that this matters, then you have bigger problems and should probably use a database instead of TOML.)

2 Likes

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.