• A Simple Web App in Rust, Part 2A
  • Joel’s Journal
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: LeopPro

Developing a Simple Web application with Rust, Part 2A

1 The whole story

If you haven’t seen part 1 of this series, start here.

In the first part, we successfully created a Rust project and wrote a “Hello World” Web application.

At first, in this section I wanted to write a program that would write dates to the file system. But ALONG the way, I struggled with type checking, so I’ll focus on that in this section.

2 start

Last time wasn’t so bad. But when I did this part before, I remember that was the hardest part.

Let’s start by moving the existing main.rs so that we can use a new file.

$ pwd
/Users/joel/Projects/simple-log
$ cd src/
$ ls
main.rs
$ mv main.rs web_main.rs
$ touch main.rs
Copy the code

3 Recall “Hello World”

Can I write “Hello World” without any reference?

Let me try:

fn main() { println! ("Hello, world");
}
Copy the code

And then:

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) Running ` target/debug/simple - log ` Hello, worldCopy the code

Oh, I think I remember it. I’m just a little unsure if I need to write println! Import something, now, that’s not necessary.

Naive approach

Okay, here we go. Search for “Rust create File” online and I find STD ::fs::File: doc.rust-lang.org/std/fs/stru… . Let’s try an example:

use std::fs::File;

fn main() {
    letmut f = try! (File::create("foo.txt"));
}
Copy the code

Compile:

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) < STD macros > : 8: "error: mismatched types: expected `()`, found `core::result::Result<_, _>` (expected (), found enum `core::result::Result`) [E0308] <std macros>:5return $ 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:17: 5:46 note: expansion site
error: aborting due to previous error
Could not compile `simple-log`.
Copy the code

Fixing this bug took me a long time when I wrote the first version. I’m not very active in the community anymore, so the solutions to these problems can be crude. I was impressed to figure it out, so I knew the answer right away.

The problem with the above code is that try! The macro’s expansion code returns an Err type in the case of an error. However, main returns the unit type (()) 1, which results in a type error.

I find three things hard to understand:

  1. At this point, I’m not quite sure what to make of the error message. What do ‘expected’ and ‘found’ mean? After I learned the answer, I understood that ‘expected’ meantmainI can clearly understand ‘expected’ and ‘found’.
  2. For me, look upThe documentIt didn’t immediately make sense to metry!Affects the return value of the function that calls it. Of course, I shouldreturnDefinition in macros. However, when I found one inRust documentI understand why in this casetry!Not in themainIn the call.
  3. This error can actually be seen in macros. I didn’t see it at the time, but the Rust compiler can output macroscopically extended source code. This feature makes such problems easy to debug.

In point 3, we mentioned extension macros. Looking at extension macros is a very effective way to debug such problems, and it’s worth digging deeper.

5 Debug extension macros

First, I looked up this code by searching for the “Rust extension macro” :

use std::fs::File;

fn main() {
    letmut f = try! (File::create("foo.txt"));
}
Copy the code

… We can make the compiler print macro extensions in this way:

$ rustc src/main.rs --pretty=expanded -Z unstable-options
#! [feature(no_std)]
#! [no_std]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;
use std::fs::File;

fn main() {
    let mut f =
        match File::create("foo.txt") {
            ::std::result::Result::Ok(val) => val,
            ::std::result::Result::Err(err) => {
                return ::std::result::Result::Err(::std::convert::From::from(err))
            }
        };
}
Copy the code

This time it’s easy to debug. Macros are very effective tools, but like any tool, we need to know when and how to use them.

Take a look at the return definition in the output above, where the problem lies. It tries to return an Err result, but main expects a unit type.

6. Fight the genre

I need to know how to solve this type of problem. I tried to imitate Try! Macro, but this time only returns the unit type:

use std::fs::File;

fn main() {
    match File::create("foo.txt") {
        Ok(val) => val,
        Err(err) => ()
    }
}
Copy the code

Run:

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: 5:5: "error: match arms have incompatible types: expected `std::fs::File`, found `()` (expected struct `std::fs::File`, found ()) [E0308] src/main.rs:5 match File::create("foo.txt") {
src/main.rs:6         Ok(val) => val,
src/main.rs:7         Err(err) => ()
src/main.rs:8     }
src/main.rs:7:21: 7:23 note: match arm with an incompatible type
src/main.rs:7         Err(err) => ()
                                  ^~
error: aborting due to previous error
Could not compile `simple-log`.
Copy the code

I don’t know how to say, “Nothing should be done here.” I guess val must be of type STD ::fs::File, so the compiler deduces that all branches of match should return it. Can I make the Ok branch return nothing, too?

use std::fs::File;

fn main() {
    match File::create("foo.txt") {
        Ok(val) => (),
        Err(err) => ()
    }
}
Copy the code

Run:

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: ": so the warning: unused variable: `val`,#[warn(unused_variables)] on by default
src/main.rs:6         Ok(val) => (),
                         ^~~
src/main.rs:7:13: 7:16 warning: unused variable: `err`, #[warn(unused_variables)] on by default
src/main.rs:7         Err(err) => ()
                          ^~~
     Running `target/debug/simple-log`
$ ls
Cargo.lock      Cargo.toml      foo.txt         src             target
Copy the code

It creates foo.txt! Sure, the code could be more elegant, but it’s pretty good right now. Let’s try something else:

use std::fs::File;

fn main() {
    File::create("foo.txt")}Copy the code

= >

Rm $foo. TXT $cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: 5:5: 5:28 error: mismatched types: expected `()`, found `core::result::Result<std::fs::File, std::io::error::Error>` (expected (), found enum `core::result::Result`) [E0308] src/main.rs:5 File::create("foo.txt")
                  ^~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `simple-log`.

To learn more, run the command again with --verbose.
Copy the code

I’ve seen this before. This means that main returns the result of File::create. I don’t think I should return anything here, but I’m not thinking in that direction. What if I add a semicolon?

use std::fs::File;

fn main() {
    File::create("foo.txt");
}
Copy the code

= >

Rm $foo. TXT $cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: 5:5: 5:29 warning: unused resultwhich must be used, #[warn(unused_must_use)] on by default

src/main.rs:5     File::create("foo.txt");
                  ^~~~~~~~~~~~~~~~~~~~~~~~
     Running `target/debug/simple-log`
$ ls
Cargo.lock      Cargo.toml      foo.txt         src             target
Copy the code

Ok, we ran successfully and created the file, but now we have an “unused result” warning. Let’s do something about this result:

use std::fs::File;

fn main() {
    match File::create("foo.txt") { Ok(val) => println! ("File created!"), Err(err) => println! ("Error: could not create file.")}}Copy the code

= >

Rm $foo. TXT $cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: ": 6:15 warning: unused variable: `val`,#[warn(unused_variables)] on by defaultsrc/main.rs:6 Ok(val) => println! ("File created!"),
                         ^~~
src/main.rs:7:13: 7:16 warning: unused variable: `err`, #[warn(unused_variables)] on by defaultsrc/main.rs:7 Err(err) => println! ("Error: could not create file.")
                          ^~~
     Running `target/debug/simple-log`
File created!
Copy the code

An unused variable warning now appears. My hunch is that we can solve this problem by using ellipses or deleting variable names:

use std::fs::File;

fn main() {
    match File::create("foo.txt") { Ok(..) => println! ("File created!"), Err(..) => println! ("Error: could not create file.")}}Copy the code

= >

Cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) Running ` target/debug/simple - log ` File created!Copy the code

Look, using the ellipsis works, so what happens if I delete the ellipsis?

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: ":" error: nullary enum variants are written with no trailing `( )` src/main.rs:6 Ok() => println! ("File created!"), ^ src/main.rs:7:13: 7:14 error: nullary enum variants are written with no trailing `( )` src/main.rs:7 Err() => println! ("Error: could not create file.")
                          ^
error: aborting due to 2 previous errors
Could not compile `simple-log`.
Copy the code

This is not what I expected. I guess “nullary” means an empty tuple that needs to be deleted. If I delete the parentheses:

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: thus: "the error: this pattern has 0 fields, but the corresponding variant has 1 field [ E0023] src/main.rs:6 Ok => println! ("File created!"), ^~ src/main.rs:7:9: 7:12 error: this pattern has 0 fields, but the corresponding variant has 1 field [ E0023] src/main.rs:7 Err => println! ("Error: could not create file.")
                      ^~~
error: aborting due to 2 previous errors
Could not compile `simple-log`.

To learn more, run the command again with --verbose.
Copy the code

It makes sense. It’s basically what I expected. My mind is forming!

7 Writing files

Let’s try something harder. How about this:

  1. Attempt to create log files. Create a log file if it does not exist.
  2. Try writing a string to the log file.
  3. Straighten everything out.

Before we’re halfway through the first example, let’s move on:

use std::fs::File;

fn log_something(filename, string) {
    letmut f = try! (File::create(filename)); try! (f.write_all(string)); } fnmain() {
    match log_something("log.txt"."ITS ALIVE!!!") { Ok(..) => println! ("File created!"), Err(..) => println! ("Error: could not create file.")}}Copy the code

= >

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

So I think the function argument must declare the type:

use std::fs::File;

fn log_something(filename: &'static str, string: &'static str) {
    letmut f = try! (File::create(filename)); try! (f.write_all(string)); } fnmain() {
    match log_something("log.txt"."ITS ALIVE!!!") { Ok(..) => println! ("File created!"), Err(..) => println! ("Error: could not create file.")}}Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) < STD macros > : 8: "error: mismatched types: expected `()`, found `core::result::Result<_, _>` (expected (), found enum `core::result::Result`) [E0308] <std macros>:5return $ 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 inscope 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: inexpansion 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.
Copy the code

There were a lot of mistakes. Let’s look at the first error, where I guess the log_something function needs to specify a return value. I tried something, but I got stuck. Turn to a search engine!

A few minutes passed, and I finally found the answer. I found some information on GitHub, but it didn’t work. I tried about 50 different solutions before settling on:

use std::io::prelude::*;
use std::fs::File;

fn log_something(filename: &'static str, string: &'static str) -> Result<File,std::io::error::Error> {
    letmut f = try! (File::create(filename)); try! (f.write_all(string)); } fnmain() {
    match log_something("log.txt"."ITS ALIVE!!!") { Ok(..) => println! ("File created!"), Err(..) => println! ("Error: could not create file.")}}Copy the code

I don’t know why it works. If I understand correctly, the arguments to the returned value of type Result should be File and STD :: IO ::error:: error. What on earth does that mean? The two types seem strange to me, the actual result (file) and the Error type. Why is that? I figured, once I fixed the rest of the bugs, this would have to be fixed again.

Now when I try to run it, I get the following error message:

$cargo run the 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)); ^~~~~~ 
      
       :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.
      Copy the code

I saw in the example that they added a B Ok to the string, which I ignored just to see what would happen. Repair 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> {
    letmut f = try! (File::create(filename)); try! (f.write_all(string)); } fnmain() {
    match log_something("log.txt"."ITS ALIVE!!!") { Ok(..) => println! ("File created!"), Err(..) => println! ("Error: could not create file.")}}Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: 4:8 5: wherein 6 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`.
Copy the code

I knew there would be problems. Spend some time researching.

The Rust documentation has a chapter on Result. It looks like I’m doing something unconventional. I mean, it seems like the “best” way to deal with the current mistake, but I’m confused. I’ve seen this unwrap a few times and it looks like it might be what I want. If I had tried unwrap, things might have been 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!!!")}Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: ": when warning: unused resultwhich 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!!!
Copy the code

Look, it worked despite a warning. But I think that’s not the way Rust is, which advocates failing early or throwing errors.

The real problem is try! And the try! The macro returns a branch of odd Result:

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

This means that whatever I pass in must implement a From:: From feature on the enumeration. But I don’t really know how features or enumerations work, and I think the whole thing is overkill for me.

I went to check the document of Result, and it seems that I went in the wrong direction: doc.rust-lang.org/std/result/. The IO ::Result example here seems similar to what I did, so let me see if I can solve the problem:

use std::io::prelude::*;
use std::fs::File;
use std::io;

fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()> {
    letmut f = try! (File::create(filename)); try! (f.write_all(string)); } fnmain() {
    match log_something("log.txt", b"ITS ALIVE!!!") { Ok(..) => println! ("File created!"), Err(..) => println! ("Error: could not create file.")}}Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: 5-1: speak the error: not all control pathsreturn a value [E0269]
src/main.rs:5 fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()>
 {
src/main.rs:6     letmut 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 thecommand again with --verbose.
Copy the code

After thinking about it for a while, I found a problem: I had to add an OK(()) statement at the end of log_something. I came to this conclusion by referring to the Result documentation.

I’m used to non-semicolon statements at the end of functions meaning return (); And the error message “not all branches return the same type” is hard to understand — to me, it’s a type mismatch. Of course, () may not be a value, which I still think is confusing.

Our final result (this article) :

use std::io::prelude::*;
use std::fs::File;
use std::io;

fn log_something(filename: &'static str, string: &'static [u8; 12]) -> io::Result<()> {
    letmut f = try! (File::create(filename)); try! (f.write_all(string)); Ok(()) } fnmain() {
    match log_something("log.txt", b"ITS ALIVE!!!") { Ok(..) => println! ("File created!"), Err(..) => println! ("Error: could not create file.")}}Copy the code

= >

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

All right, it’s working. Zap! I want to end this chapter here because it has been very challenging. I’m sure this code could have been improved, but I’ll stop there and look at dates and times in Rust in the next chapter.

8 update

  1. NMSpaz pointed out an error in my example on Reddit.

Article series: Developing a Simple Web application using Rust

  • Part 1
  • The second part a
  • The second part b
  • Part 3
  • Part 4
  • conclusion

Footnote:

1 Hahahaha.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.