[譯]使用 Rust 開發一個簡單的 Web 應用,第 2a 部分

使用 Rust 開發一個簡單的 Web 應用,第 2a 部分

1 前因後果

若是你還沒看過這個系列的第一部分,請從這裏開始。html

在第一部分,咱們成功的建立了一個 Rust 工程而且編寫了一個「Hello World」 Web 應用。前端

起初,在這個部分中我想寫一個能夠將日期寫入文件系統的程序。可是在這個過程當中,我和類型檢查鬥爭了很久,因此這個部分主要寫這個。android

2 開始

上一次還不算太糟。但當我以前作這部分的時候,我記得這是最難的部分。ios

讓咱們從移動已存在的 main.rs 開始,這樣咱們就可使用一個新文件。git

$ pwd
/Users/joel/Projects/simple-log
$ cd src/
$ ls
main.rs
$ mv main.rs web_main.rs
$ touch main.rs
複製代碼

3 回憶「Hello World」

我能夠不借助任何參考獨立寫出「Hello World」麼?github

讓我試試:web

fn main() {
    println!("Hello, world");
}
複製代碼

而後:後端

$ cargo run
   Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
     Running `target/debug/simple-log`
Hello, world
複製代碼

哦,我想我還記得它。我只是有一點不太確定,我是否須要爲 println! 導入些什麼,如今看來,這是沒必要要的。bash

4 天真的方法

好了,繼續。上網搜索「Rust 建立文件」,我找到了 std::fs::Filedoc.rust-lang.org/std/fs/stru…。讓咱們來試試一個例子:app

use std::fs::File;

fn main() {
    let mut f = try!(File::create("foo.txt"));
}
複製代碼

編譯:

$ 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:5:17: 5:46 note: expansion site
error: aborting due to previous error
Could not compile `simple-log`.
複製代碼

當我編寫第一個版本的時候,解決這個錯誤真是花費了我不少時間。我再也不常常活躍於社區,因此這些問題的解決方案多是比較粗糙的。弄清楚這個問題給我留下了深入的印象,因此我立刻就知道答案了。

上面代碼的問題是 try! 宏的展開代碼在出現錯誤的狀況下返回 Err 類型。然而 main 返回單元類型(()1,這會致使一個類型錯誤。

我認爲有三點難以理解:

  1. 在這一點上,我不是很肯定要如何理解錯誤信息。'expected' 和 'found'都指什麼?我知道答案以後,我明白 'expected' 指的是 main 的返回值,我能夠清楚的明白 'expected' 和 'found'。
  2. 於我而言,查閱文檔並無讓我馬上明白 try! 是如何影響調用它的函數返回值的。固然,我應該查閱 return 在宏中的定義。然而,當我找到一個在 Rust 文檔 中的評論時,我才明白在這個例子中爲何 try! 不能在 main 中調用。
  3. 這個錯誤事實上能夠在宏中體現。我當時沒明白,但 Rust 編譯器能夠輸出通過宏擴展後的源代碼。這個功能讓這類問題易於調試。

在第三點中,咱們提到了擴展宏。查閱擴展宏是調試這類問題很是有效的方法,這值得咱們深究。

5 調試擴展宏

首先,我經過搜索「Rust 擴展宏」查閱到這些代碼:

use std::fs::File;

fn main() {
    let mut f = try!(File::create("foo.txt"));
}
複製代碼

……咱們能夠經過這種方式使編譯器輸出宏擴展:

$ 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))
            }
        };
}
複製代碼

此次就易於調試了。宏是很是有效的工具,可是同其餘工具同樣,咱們要知道什麼時候、怎樣使用它們。

看看吧,上面輸出中的 return 定義,問題就出在這裏。它嘗試返回一個 Err 結果,可是 main 函數指望獲得一個單元類型。

6 和類型鬥爭

我要知道如何解決這個類型問題。我嘗試模仿 try! 宏,可是此次只返回單元類型:

use std::fs::File;

fn main() {
    match File::create("foo.txt") {
        Ok(val) => val,
        Err(err) => ()
    }
}
複製代碼

運行:

$ cargo run
   Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:5:5: 8:6 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`.
複製代碼

我不知道該如何表述,「這裏不該該作任何事」。我猜 val 必定是 std::fs::File 類型,因此編譯器推斷 match全部分支都應該返回它。我能夠令 Ok 分支也不返回任何東西麼?

use std::fs::File;

fn main() {
    match File::create("foo.txt") {
        Ok(val) => (),
        Err(err) => ()
    }
}
複製代碼

運行:

$ cargo run
   Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:12: 6:15 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
複製代碼

它建立了 foo.txt!固然,代碼能夠更優雅,可是它如今很不錯。讓咱們試試其餘的方法:

use std::fs::File;

fn main() {
    File::create("foo.txt")
}
複製代碼

=>

$ rm foo.txt
$ cargo run
   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.
複製代碼

我以前看到過這個。它的意思是 main 函數返回了 File::create 的結果。我想,這裏不該該返回任何東西,可是我沒有往這個方向思考。若是我添加一個分號呢?

use std::fs::File;

fn main() {
    File::create("foo.txt");
}
複製代碼

=>

$ rm foo.txt
$ cargo run
   Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:5:5: 5:29 warning: unused result which 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
複製代碼

好了,咱們成功運行且建立了文件,可是如今有一個「未使用的結果」警告。讓咱們去作點什麼來處理這個結果:

use std::fs::File;

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

=>

$ rm foo.txt
$ cargo run
   Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:12: 6:15 warning: unused variable: `val`, #[warn(unused_variables)] on by default
src/main.rs:6         Ok(val) => println!("File created!"),
                         ^~~
src/main.rs:7:13: 7:16 warning: unused variable: `err`, #[warn(unused_variables)] on by default
src/main.rs:7         Err(err) => println!("Error: could not create file.")
                          ^~~
     Running `target/debug/simple-log`
File created!
複製代碼

如今出現了未使用的變量警告。個人直覺是,咱們可使用省略號或刪除變量名來解決這個問題:

use std::fs::File;

fn main() {
    match File::create("foo.txt") {
        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)
     Running `target/debug/simple-log`
File created!
複製代碼

看,使用省略號可行,那麼若是我刪除省略號會發生什麼?

$ cargo run
   Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:12: 6:13 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`.
複製代碼

這和我預想的不同。我猜想「nullary」的意思是空元組,它須要被刪除。若是我把括號刪掉:

$ cargo run
   Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:6:9: 6:11 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.
複製代碼

這很好理解,基本上是我所預想的。個人思惟正在造成!

7 寫入文件

讓咱們嘗試一些更難的東西。這個怎麼樣:

  1. 嘗試建立日誌文件。若是日誌文件不存在則建立它。
  2. 嘗試寫一個字符串到日誌文件。
  3. 把一切理清。

第一個例子進度尚未過半,咱們繼續:

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.
$
複製代碼

因此我想函數參數必需要聲明類型:

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.
複製代碼

出現了好多錯誤。咱們看第一個錯誤,我猜 log_something 函數須要規定一個返回值。我嘗試了一些方法,可是我卡住了。求助於搜索引擎!

幾分鐘過去了,我終於找到了答案。我在 GitHub 上找到了一些相關信息,可是它並不有效。我大約嘗試了 50 種不一樣方案,最終得以解決:

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.")
    }
}
複製代碼

我不知道它能正常工做的緣由。若是我理解的正確,返回值的類型 Result 的參數應該是 Filestd::io::error::Error。這到底是什麼意思?對我來講,這兩種類型很奇怪,一種是實際結果(文件),另外一種是 Error 類型。爲何?我想,我修復了剩餘錯誤以後,這還須要再次修復。

如今當我嘗試運行它時,我獲得以下錯誤信息:

$ 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. 複製代碼

我在例子中看到他們在字符串前加了一個 b Ok,我忽略它只是爲了看看會發生什麼。修復參數:

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`.
複製代碼

我知道這將出現問題。花一些時間查閱資料。

Rust 文檔有一章介紹 Result。看起來我作了一些很是規的操做。我是說,這彷佛是「最好」的方式來處理當前的錯誤,可是我很疑惑。我曾見過這個 unwrap 幾回,這看起來多是我想要的。若是我嘗試 unwrap,事情可能會有所不一樣:

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!!!
複製代碼

看,雖然有一個警告,它仍是工做了。但我想,這不是 Rust 的方式,Rust 提倡提早失敗或拋出錯誤。

真正的問題是 try!try! 宏返回古怪 Result 的分支:

return $crate::result::Result::Err($crate::convert::From::from(err))
複製代碼

這意味着不管我傳入什麼都必須在枚舉上實現一個 From::from 特徵。可是我真的不知道特徵或枚舉是如何工做的,並且我認爲整個事情對於我來講都是矯枉過正。

我去查閱 Result 的文檔,看起來我走錯了方向:doc.rust-lang.org/std/result/。這裏的 io::Result 例子彷佛於我作的類似,因此讓我看看我是否能解決問題:

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.
複製代碼

通過一段時間的思考,我發現了問題:必須在 log_something 的最後添加 OK(()) 語句。我經過參考 Result 文檔得出這樣的結論。

我已經習慣了在函數最後的無分號語句意思是 return ();而錯誤消息「不是全部的分支都返回同一類型」很差理解 —— 對我來講,這是類型不匹配問題。固然,() 可能不是一個值,我仍然認爲這是使人困惑的。

咱們最終的結果(這篇文章):

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!!!
複製代碼

好了,它工做了,美滋滋!我想在這裏結束本章,由於本章內容很是富有挑戰性。我確信這個代碼是能夠作出改進的,但這裏暫告一段落,下一章咱們將研究 Rust 中的日期和時間.

8 更新

  1. NMSpaz 在 Reddit 指出了我例子中的一個錯誤。

系列文章:使用 Rust 開發一個簡單的 Web 應用

腳註:

1 哈哈哈哈。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索