Jemsurfer
Jemsurfer

Reputation: 21

Writing a Vec<String> to files using std::fs::write

I'm writing a program that handles a vector which is combination of numbers and letters (hence Vec<String>). I sort it with the .sort() method and am now trying to write it to a file.

Where strvec is my sorted vector that I'm trying to write using std::fs::write;

println!("Save results to file?");
let to_save: String = read!();
match to_save.as_str() {
    "y" => {
        println!("Enter filename");
        let filename: String = read!();
        let pwd = current_dir().into();
        write("/home/user/dl/results", strvec);

Rust tells me "the trait AsRef<[u8]> is not implemented for Vec<String>". I've also tried using &strvec. How do I avoid this/fix it?

Upvotes: 2

Views: 5505

Answers (3)

cdhowie
cdhowie

Reputation: 169528

You can't get a &[u8] from a Vec<String> without copying since a slice must refer to a contiguous sequence of items. Each String will have its own allocation on the heap somewhere, so while each individual String can be converted to a &[u8], you can't convert the whole vector to a single &[u8].

While you can .collect() the vector into a single String and then get a &[u8] from that, this does some unnecessary copying. Consider instead just iterating the Strings and writing each one to the file. With this helper, it's no more complex than using std::fs::write():

use std::path::Path;
use std::fs::File;
use std::io::Write;

fn write_each(
    path: impl AsRef<Path>,
    items: impl IntoIterator<Item=impl AsRef<[u8]>>,
) -> std::io::Result<()> {
    let mut file = File::create(path)?;
    for i in items {
        file.write_all(i.as_ref())?;
    }
    // Surface any I/O errors that could otherwise be swallowed when
    // the file is closed implicitly by being dropped.
    file.sync_all()
}

The bound impl IntoIterator<Item=impl AsRef<[u8]>> is satisfied by both Vec<String> and by &Vec<String>, so you can call this as either write_each("path/to/output", strvec) (to consume the vector) or write_each("path/to/output", &strvec) (if you need to hold on to the vector for later).

Upvotes: 2

Miokloń
Miokloń

Reputation: 315

When it comes to writing objects to the file you might want to consider serialization. Most common library for this in Rust is serde, however in this example where you want to store vector of Strings and if you don't need anything human readable in file (but it comes with small size :P), you can also use bincode:

use std::fs;
use bincode;

fn main() {
    let v = vec![String::from("aaa"), String::from("bbb")];
    let encoded_v = bincode::serialize(&v).expect("Could not encode vector");
    fs::write("file", encoded_v).expect("Could not write file");

    let read_v = fs::read("file").expect("Could not read file");
    let decoded_v: Vec<String> = bincode::deserialize(&read_v).expect("Could not decode vector");

    println!("{:?}", decoded_v);
}

Remember to add bincode = "1.3.3" under dependencies in Cargo.toml

#EDIT: Actually you can easily save String to the file so simple join() should do:

use std::fs;

fn main() {
    let v = vec![
        String::from("aaa"),
        String::from("bbb"),
        String::from("ccc")];

    fs::write("file", v.join("\n")).expect("");
}

Upvotes: 6

Jeremy Meadows
Jeremy Meadows

Reputation: 2581

Rust can't write anything besides a &[u8] to a file. There are too many different ways which data can be interpreted before it gets flattened, so you need to handle all of that ahead of time. For a Vec<String>, it's pretty simple, and you can just use concat to squish everything down to a single String, which can be interpreted as a &[u8] because of its AsRef<u8> trait impl.

Another option would be to use join, in case you wanted to add some sort of delimiter between your strings, like a space, comma, or something.

fn main() {
    let strvec = vec![
        "hello".to_string(),
        "world".to_string(),
    ];

    // "helloworld"
    std::fs::write("/tmp/example", strvec.concat()).expect("failed to write to file");
    // "hello world"
    std::fs::write("/tmp/example", strvec.join(" ")).expect("failed to write to file");
}

Upvotes: 1

Related Questions