I'm new to Rust and, coming from Java world, I wanted to play with Rust trait as I would do with Java interfaces. I imagined the following need :
- I must be able to save User (firstname, lastname) somewhere (in a db, a file)
- I can fetch all of them
I started to define the trait that I wanted to have :
trait UserDb {
fn get_all(&self) -> Result<Vec<User>, io::Error>;
fn save(&mut self, user: &User) -> Result<(), io::Error>;
}
You can see that when I declare get_all
function, I don't mention the need to have a mutable borrow on self
(i.e &mut self
).
Then I decided to implement this trait with File capabilities (please find the full code at the end).
What surprised me is that, when I read the content of the file, I have to declare self
as mutable. (here's a reason why : Why does a File need to be mutable to call Read::read_to_string?)
It annoys me because if I do that, I must declare in the trait self
as mutable, even if I'm reading data. I feel like there is a leak of implementation detail in the trait.
I think my approach is not valid or not idiomatic in Rust. How would you achieve this ?
Here is the full code :
///THIS CODE DOESNT COMPILE
///THE COMPILER TELLS TO MAKE self AS MUTABLE
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::path::Path;
use std::io::Read;
use std::io::Write;
struct User {
pub firstname: String,
pub lastname: String,
}
trait UserDb {
fn get_all(&self) -> Result<Vec<User>, io::Error>;
fn save(&mut self, user: &User) -> Result<(), io::Error>;
}
struct FsUserDb {
pub file: File,
}
impl FsUserDb {
fn new(filename: &str) -> Result<FsUserDb, io::Error> {
if Path::new(filename).exists() {
let file = OpenOptions::new()
.append(true)
.write(true)
.open(filename)?;
Ok(FsUserDb { file })
} else {
Ok(FsUserDb {
file: File::create(filename)?,
})
}
}
}
impl UserDb for FsUserDb {
fn get_all(&self) -> Result<Vec<User>, io::Error> {
let mut contents = String::new();
self.file.read_to_string(&mut contents)?;
let users = contents
.lines()
.map(|line| line.split(";").collect::<Vec<&str>>())
.map(|split_line| User {
firstname: split_line[0].to_string(),
lastname: split_line[1].to_string(),
})
.collect();
Ok(users)
}
fn save(&mut self, user: &User) -> Result<(), io::Error> {
let user_string =
format!("{},{}", user.firstname, user.lastname);
match self.file.write(user_string.as_bytes()) {
Ok(_) => Ok(()),
Err(e) => Err(e)
}
}
}
fn main() {
let db = FsUserDb::new("/tmp/user-db");
}