Help to fix Serialize not implemented for std::io::Error

I'm following a tutorial on Complete the main module - Training | Microsoft Learn, but I'm facing a challenge and can't seem to figure out how to fix it. Here's the error.
I'm now learning rust.

serde_json::to_writer_pretty(file, &tasks)?;
     |     ----------------------------       ^^^^^^ the trait `Serialize` is not implemented for `std::io::Error`
use chrono::{serde::ts_seconds, DateTime, Local, Utc};
use serde::Deserialize;
use serde::Serialize;
use std::fmt;
use std::fs::{File, OpenOptions};
use std::io::{Error, ErrorKind, Result, Seek, SeekFrom}; // Include the `Error` type.
use std::path::PathBuf;

#[derive(Debug, Deserialize, Serialize)]
pub struct Task {
    pub text: String,

    #[serde(with = "ts_seconds")]
    pub created_at: DateTime<Utc>,
}

impl Task {
    pub fn new(text: String) -> Task {
        let created_at: DateTime<Utc> = Utc::now();
        Task { text, created_at }
    }
}

impl fmt::Display for Task {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let created_at = self.created_at.with_timezone(&Local).format("%F %H:%M");
        write!(f, "{:<50} [{}]", self.text, created_at)
    }
}

pub fn add_task(journal_path: PathBuf, task: Task) -> Result<()> {
    // Open the file.
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(journal_path)?;

    // Consume the file's contents as a vector of tasks.
    let mut tasks = collect_tasks(&file);

    // Write the modified task list back into the file.
    tasks?.push(task);
    serde_json::to_writer_pretty(file, &tasks)?;

    Ok(())
}

pub fn complete_task(journal_path: PathBuf, task_position: usize) -> Result<()> {
    // Open the file.
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(journal_path)?;

    // Consume file's contents as a vector of tasks.
    let mut tasks = collect_tasks(&file)?;

    // Try to remove the task.
    if task_position == 0 || task_position > tasks.len() {
        return Err(Error::new(ErrorKind::InvalidInput, "Invalid Task ID"));
    }
    tasks.remove(task_position - 1);

    // Write the modified task list back into the file.
    file.set_len(0)?;
    serde_json::to_writer(file, &tasks)?;
    Ok(())
}

pub fn list_tasks(journal_path: PathBuf) -> Result<()> {
    // Open the file.
    let file = OpenOptions::new().read(true).open(journal_path)?;
    // Parse the file and collect the tasks.
    let tasks = collect_tasks(&file)?;

    // Enumerate and display tasks, if any.
    if tasks.is_empty() {
        println!("Task list is empty!");
    } else {
        let mut order: u32 = 1;
        for task in tasks {
            println!("{}: {}", order, task);
            order += 1;
        }
    }

    Ok(())
}

fn collect_tasks(mut file: &File) -> Result<Vec<Task>> {
    file.seek(SeekFrom::Start(0))?; // Rewind the file before.
    let tasks = match serde_json::from_reader(file) {
        Ok(tasks) => tasks,
        Err(e) if e.is_eof() => Vec::new(),
        Err(e) => Err(e)?,
    };
    file.seek(SeekFrom::Start(0))?; // Rewind the file after.
    Ok(tasks)
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `std::io::Error: Serialize` is not satisfied
    --> src/lib.rs:44:40
     |
44   |     serde_json::to_writer_pretty(file, &tasks)?;
     |     ----------------------------       ^^^^^^ the trait `Serialize` is not implemented for `std::io::Error`
     |     |
     |     required by a bound introduced by this call
     |
     = help: the following other types implement trait `Serialize`:
               bool
               char
               isize
               i8
               i16
               i32
               i64
               i128
             and 143 others
     = note: required for `Result<Vec<Task>, std::io::Error>` to implement `Serialize`
note: required by a bound in `to_writer_pretty`
    --> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/serde_json-1.0.109/src/ser.rs:2162:17
     |
2159 | pub fn to_writer_pretty<W, T>(writer: W, value: &T) -> Result<()>
     |        ---------------- required by a bound in this function
...
2162 |     T: ?Sized + Serialize,
     |                 ^^^^^^^^^ required by this bound in `to_writer_pretty`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (lib) due to previous error

Your tasks variable is of type Result<Vec<Task>, std::io::Error>. Your program is trying to serialize the tasks if collecting them was successful and otherwise the error. This is probably not what you intended. You probably wanted to serialize the tasks by themselves. That means you need to handle the possibility of an error through something like tasks.expect("Expected to be able to collect the tasks").

2 Likes
    // Consume the file's contents as a vector of tasks.
-    let mut tasks = collect_tasks(&file);
+    let mut tasks = collect_tasks(&file)?;

    // Write the modified task list back into the file.
-    tasks?.push(task);
+    tasks.push(task);

Rust Playground

3 Likes

It compiles now
Kindly explain why it works, and why the initial code wasn't working

These 3 lines from your code are the relevant ones, all mentioning tasks.

    let mut tasks = collect_tasks(&file);
    tasks?.push(task);
    serde_json::to_writer_pretty(file, &tasks)?;

In your original form, tasks is a Result<Vec<Task>, std::io::Error>, so serde_json::to_writer_pretty is given that Result, which it can't serialize. (There is actually also an ownership problem with this too, but let's not worry about that).

With @vague's change, instead of processing the Result with the ? operator as tasks? on the second line, the program does that before assigning to tasks, so tasks is a Vec<Task> which can be serialized.

In general, it's very rarely[1] the right thing to do to assign a Result to a variable and apply ? to the variable; in most cases, you want to propagate/handle a possible error as soon as it arises — right after the function call that produced it, collect_tasks(), and only put the successful value in a variable. So when you get an error (type, trait, method lookup, whatever) telling you that some variable like tasks is a Result, you should go back to where the variable was defined and put the ? there, not on the usage of the variable.

The same principle applies to .unwrap(), and match, and all other ways of processing a Result.


  1. There are reasons to do otherwise, like if you want to do many things that might fail individually, and not let an error in one of them cancel all the rest. ↩ī¸Ž

3 Likes

Actually, even if you don't pass &task to to_writer_pretty to trigger unsatisfied std::io::Error: Serialize bound error, original code won't compile due to error[E0382]: borrow of moved value: `tasks` if you use it latter. Rust Playground

    let mut tasks = collect_tasks(&file);
    tasks?.push(task);
    &tasks;
    // serde_json::to_writer_pretty(file, &tasks)?;

error[E0382]: borrow of moved value: `tasks`
   --> src/lib.rs:42:5
    |
40  |     let mut tasks = collect_tasks(&file);
    |         --------- move occurs because `tasks` has type `Result<Vec<Task>, std::io::Error>`, which does not implement the `Copy` trait
41  |     tasks?.push(task);
    |     ------ `tasks` moved due to this method call
42  |     &tasks;
    |     ^^^^^^ value borrowed here after move
    |
note: `branch` takes ownership of the receiver `self`, which moves `tasks`
   --> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/try_trait.rs:217:15
    |
217 |     fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
    |               ^^^^

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.