Let's try something a little harder. How about this:

Try to create the log file. If it exists, great; if not, boo. Try to write a string to the log file. Clean everything up.

This first example doesn't even attempt half of that, but we'll go with it:

use std::fs::File; fn log_something(filename, string) { let mut f = try!(File::create(filename)); try!(f.write_all(string)); } fn main() { match log_something("log.txt", "ITS ALIVE!!!") { Ok(..) => println!("File created!"), Err(..) => println!("Error: could not create file.") } }

=>

$ cargo run Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log) src/main.rs:3:26: 3:27 error: expected one of `:` or `@`, found `,` src/main.rs:3 fn log_something(filename, string) { ^ Could not compile `simple-log`. To learn more, run the command again with --verbose. $

So I guess function arguments need must have type annotations:

use std::fs::File; fn log_something(filename: &'static str, string: &'static str) { let mut f = try!(File::create(filename)); try!(f.write_all(string)); } fn main() { match log_something("log.txt", "ITS ALIVE!!!") { Ok(..) => println!("File created!"), Err(..) => println!("Error: could not create file.") } }

=>

$ cargo run Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log) <std macros>:5:8: 6:42 error: mismatched types: expected `()`, found `core::result::Result<_, _>` (expected (), found enum `core::result::Result`) [E0308] <std macros>:5 return $ crate:: result:: Result:: Err ( <std macros>:6 $ crate:: convert:: From:: from ( err ) ) } } ) <std macros>:1:1: 6:48 note: in expansion of try! src/main.rs:4:17: 4:45 note: expansion site src/main.rs:5:12: 5:29 error: type `std::fs::File` does not implement any method in scope named `wr ite_all` src/main.rs:5 try!(f.write_all(string)); ^~~~~~~~~~~~~~~~~ <std macros>:1:1: 6:48 note: in expansion of try! src/main.rs:5:5: 5:31 note: expansion site src/main.rs:5:12: 5:29 help: methods from traits can only be called if the trait is in scope; the f ollowing trait is implemented but not in scope, perhaps add a `use` for it: src/main.rs:5:12: 5:29 help: candidate #1: use `std::io::Write` <std macros>:5:8: 6:42 error: mismatched types: expected `()`, found `core::result::Result<_, _>` (expected (), found enum `core::result::Result`) [E0308] <std macros>:5 return $ crate:: result:: Result:: Err ( <std macros>:6 $ crate:: convert:: From:: from ( err ) ) } } ) <std macros>:1:1: 6:48 note: in expansion of try! src/main.rs:5:5: 5:31 note: expansion site src/main.rs:10:9: 10:15 error: mismatched types: expected `()`, found `core::result::Result<_, _>` (expected (), found enum `core::result::Result`) [E0308] src/main.rs:10 Ok(..) => println!("File created!"), ^~~~~~ src/main.rs:11:9: 11:16 error: mismatched types: expected `()`, found `core::result::Result<_, _>` (expected (), found enum `core::result::Result`) [E0308] src/main.rs:11 Err(..) => println!("Error: could not create file.") ^~~~~~~ error: aborting due to 5 previous errors Could not compile `simple-log`. To learn more, run the command again with --verbose.

That's a lot of errors. Looking at the first error, I'm guessing that log_something needs to have a return value specified. I've tried a few things, but right now I'm stuck. To the search engines!

A few minutes have passed, and I finally have an answer. I did some searching on GitHub, but it wasn't fruitful. I tried about 50 different things, but got this to work:

use std::io::prelude::*; use std::fs::File; fn log_something(filename: &'static str, string: &'static str) -> Result<File,std::io::error::Error> { let mut f = try!(File::create(filename)); try!(f.write_all(string)); } fn main() { match log_something("log.txt", "ITS ALIVE!!!") { Ok(..) => println!("File created!"), Err(..) => println!("Error: could not create file.") } }

I'm not really sure why it works. If I understand correctly, the return value is of Result type that's parameterized with the types File and std::io::error::Error . What does this mean, exactly? It seems strange to me that of the two types, one type is the actual result (a file), yet the second is an Error type. Why? I'm thinking that once I fix the remaining error(s), this will need fixing again.

So, now when I try to run it, I get:

$ cargo run Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log) src/main.rs:8:22: 8:28 error: mismatched types: expected `&[u8]`, found `&'static str` (expected slice, found str) [E0308] src/main.rs:8 try!(f.write_all(string)); ^~~~~~ <std macros>:1:1: 6:48 note: in expansion of try! src/main.rs:8:5: 8:31 note: expansion site error: aborting due to previous error Could not compile `simple-log`. To learn more, run the command again with --verbose.

Ok, so I saw in the example that they prefixed the string with a b , which I neglected to do just to see what would happen. Fixing the parameters:

use std::io::prelude::*; use std::fs::File; fn log_something(filename: &'static str, string: &'static [u8; 12]) -> Result<File,std::io::error::Error> { let mut f = try!(File::create(filename)); try!(f.write_all(string)); } fn main() { match log_something("log.txt", "ITS ALIVE!!!") { Ok(..) => println!("File created!"), Err(..) => println!("Error: could not create file.") } }

=>

$ cargo run Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log) src/main.rs:4:85: 4:106 error: struct `Error` is private src/main.rs:4 fn log_something(filename: &'static str, string: &'static [u8; 12]) -> Result<File, std::io::error::Error> { ^~~~~~~~~~~~~~~~~~~~~ error: aborting due to previous error Could not compile `simple-log`.

Ugh – I knew this was going to be a problem. Time to do some searching and reading.

The Rust book has a section on Result . Hmm. It seems like what I'm doing may not be idiomatic? I'd say that it seems like the "best" way to handle what is going on, but I am confused. I've seen this unwrap thing a few times, and it seems like it could be what I want. If I try unwrap, things might be different:

fn log_something(filename: &'static str, string: &'static [u8; 12]) { let mut f = File::create(filename).unwrap(); f.write_all(string); } fn main() { log_something("log.txt", b"ITS ALIVE!!!") }

=>

$ cargo run Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log) src/main.rs:6:5: 6:25 warning: unused result which must be used, #[warn(unused_must_use)] on by def ault src/main.rs:6 f.write_all(string); ^~~~~~~~~~~~~~~~~~~~ Running `target/debug/simple-log` $ ls Cargo.lock Cargo.toml foo.txt log.txt src target $ cat log.txt ITS ALIVE!!!

So, that worked, although there is a warning. I think this is not "the Rust way", since its failing early/throwing errors away.

The real problem with try! and returning a Result is that there's this weirdness dealing with this line in the try! macro:

return $crate::result::Result::Err($crate::convert::From::from(err))

This means that whatever I pass in has to have a From::from trait implemented on an enum, but I really have no idea how traits or enums work, and I think the whole thing is overkill anyway for what I'm trying to do.

I've gone to the documentation for Result , and it looks like I may be going in the wrong direction: https://doc.rust-lang.org/std/result/. This io::Result example seems to be similar enough to what I'm doing, so let me see if I can fix that up:

use std::io::prelude::*; use std::fs::File; use std::io; fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()> { let mut f = try!(File::create(filename)); try!(f.write_all(string)); } fn main() { match log_something("log.txt", b"ITS ALIVE!!!") { Ok(..) => println!("File created!"), Err(..) => println!("Error: could not create file.") } }

=>

$ cargo run Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log) src/main.rs:5:1: 8:2 error: not all control paths return a value [E0269] src/main.rs:5 fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()> { src/main.rs:6 let mut f = try!(File::create(filename)); src/main.rs:7 try!(f.write_all(string)); src/main.rs:8 } error: aborting due to previous error Could not compile `simple-log`. To learn more, run the command again with --verbose.

After some time thinking, I see the problem: an Ok(()) statement must be added as the final statement in log_something . I realized this because I saw that this is how things happen in the Result documentation.

I've been used to the idea that not having something after the final semicolon means return () ; however, the message "not all control paths return a value" doesn't make sense – to me, this is a type mismatch. Unless, of course, () is not a value, which it might not be, but I still think that's confusing.

Our final result (for this post):

use std::io::prelude::*; use std::fs::File; use std::io; fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()> { let mut f = try!(File::create(filename)); try!(f.write_all(string)); Ok(()) } fn main() { match log_something("log.txt", b"ITS ALIVE!!!") { Ok(..) => println!("File created!"), Err(..) => println!("Error: could not create file.") } }

=>

$ rm log.txt $ cargo run Running `target/debug/simple-log` File created! $ cat log.txt ITS ALIVE!!!

Ok, it works. Great. I'm going to end here because this has been pretty challenging. I'm sure improvements could be made on this code, but this is a good stopping point and a good time to research dates and times in Rust, which will be the the next post.