- 原文地址:A Simple Web App in Rust, Part 2a
- 原文作者:Joel's Journal
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:LeopPro
使用 Rust 開發一個簡單的 Web 應用,第 2a 部分
1 來龍去脈
如果你還沒看過這個系列的第一部分,請從這裡開始。
在第一部分,我們成功的建立了一個 Rust 工程並且編寫了一個“Hello World” Web 應用。
起初,在這個部分中我想寫一個可以將日期寫入檔案系統的程式。但是在這個過程中,我和型別檢查鬥爭了好久,所以這個部分主要寫這個。
2 開始
上一次還不算太糟。但當我之前做這部分的時候,我記得這是最難的部分。
讓我們從移動已存在的 main.rs
開始,這樣我們就可以使用一個新檔案。
$ 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”麼?
讓我試試:
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!
匯入些什麼,現在看來,這是不必要的。
4 天真的方法
好了,繼續。上網搜尋“Rust 建立檔案”,我找到了 std::fs::File
:doc.rust-lang.org/std/fs/stru…。讓我們來試試一個例子:
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,這會導致一個型別錯誤。
我認為有三點難以理解:
- 在這一點上,我不是很確定要如何理解錯誤資訊。'expected' 和 'found'都指什麼?我知道答案之後,我明白 'expected' 指的是
main
的返回值,我可以清楚的明白 'expected' 和 'found'。 - 於我而言,查閱文件並沒有讓我立刻明白
try!
是如何影響呼叫它的函式返回值的。當然,我應該查閱return
在巨集中的定義。然而,當我找到一個在 Rust 文件 中的評論時,我才明白在這個例子中為什麼try!
不能在main
中呼叫。 - 這個錯誤事實上可以在巨集中體現。我當時沒明白,但 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 寫入檔案
讓我們嘗試一些更難的東西。這個怎麼樣:
- 嘗試建立日誌檔案。如果日誌檔案不存在則建立它。
- 嘗試寫一個字串到日誌檔案。
- 把一切理清。
第一個例子進度還沒有過半,我們繼續:
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
的引數應該是 File
和 std::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 更新
- NMSpaz 在 Reddit 指出了我例子中的一個錯誤。
—
系列文章:使用 Rust 開發一個簡單的 Web 應用
腳註:
1 哈哈哈哈。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。