- 原文地址:A Simple Web App in Rust, Part 3 -- Integration
- 原文作者:Joel's Journal
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:LeopPro
- 校對者:ryouaki
使用 Rust 開發一個簡單的 Web 應用,第 3 部分 —— 整合
1 前情回顧
這是使用 Rust 開發一個簡單的 Web 應用系列的第 3 部分.
到目前為止,我們已經有了一些最簡可行功能在幾個 Rust 原始檔中。現在,我們想把它們放在一個應用程式中。
1.1 Review
我們將以下兩個模組整合在一起:檔案寫入 / 記錄程式碼,Web 服務程式碼。讓我們 Review 一下它們:
首先,檔案記錄程式碼:
extern crate chrono;
use std::io::prelude::*;
use std::fs::{File,OpenOptions};
use std::io;
use chrono::{DateTime,Local};
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
let mut file = try!(OpenOptions::new().
append(true).
write(true).
create(true).
open(filename));
try!(file.write_all(bytes));
Ok(())
}
fn log_time(filename: &'static str) -> io::Result<()> {
let entry = formatted_time_entry();
let bytes = entry.as_bytes();
try!(record_entry_in_log(filename, &bytes));
Ok(())
}
fn main() {
match log_time("log.txt") {
Ok(..) => println!("File created!"),
Err(e) => println!("Error: {}", e)
}
}
複製程式碼
現在,Web 服務程式碼:
#[macro_use] extern crate nickel;
use nickel::Nickel;
fn say_hello() -> &'static str {
"Hello dear world!"
}
fn main() {
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
say_hello()
}
});
server.listen("127.0.0.1:6767");
}
複製程式碼
2 整合程式碼:和型別系統作鬥爭
好了,我想整合這兩個程式。首先我會將它們放到一個檔案中(當然,要將它們其中之一的 main
函式名字改一下),看一看是否能成功編譯。
#[macro_use] extern crate nickel;
extern crate chrono;
use std::io::prelude::*;
use std::fs::{File,OpenOptions};
use std::io;
use chrono::{DateTime,Local};
use nickel::Nickel;
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
let mut file = try!(OpenOptions::new().
append(true).
write(true).
create(true).
open(filename));
try!(file.write_all(bytes));
Ok(())
}
fn log_time(filename: &'static str) -> io::Result<()> {
let entry = formatted_time_entry();
let bytes = entry.as_bytes();
try!(record_entry_in_log(filename, &bytes));
Ok(())
}
fn main2() {
match log_time("log.txt") {
Ok(..) => println!("File created!"),
Err(e) => println!("Error: {}", e)
}
}
fn say_hello() -> &'static str {
"Hello dear world!"
}
fn main() {
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
say_hello()
}
});
server.listen("127.0.0.1:6767");
}
複製程式碼
編譯執行:
$ cargo run
src/main.rs:5:15: 5:19 warning: unused import, #[warn(unused_imports)] on by default
src/main.rs:5 use std::fs::{File,OpenOptions};
^~~~
src/main.rs:11:1: 15:2 warning: function is never used: `formatted_time_entry`, #[warn(dead_code)] o
n by default
src/main.rs:11 fn formatted_time_entry() -> String {
src/main.rs:12 let local: DateTime<Local> = Local::now();
src/main.rs:13 let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
src/main.rs:14 formatted
src/main.rs:15 }
src/main.rs:17:1: 25:2 warning: function is never used: `record_entry_in_log`, #[warn(dead_code)] on
by default
src/main.rs:17 fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
src/main.rs:18 let mut file = try!(OpenOptions::new().
src/main.rs:19 append(true).
src/main.rs:20 write(true).
src/main.rs:21 create(true).
src/main.rs:22 open(filename));
...
src/main.rs:27:1: 33:2 warning: function is never used: `log_time`, #[warn(dead_code)] on by default
src/main.rs:27 fn log_time(filename: &'static str) -> io::Result<()> {
src/main.rs:28 let entry = formatted_time_entry();
src/main.rs:29 let bytes = entry.as_bytes();
src/main.rs:30
src/main.rs:31 try!(record_entry_in_log(filename, &bytes));
src/main.rs:32 Ok(())
...
src/main.rs:35:1: 40:2 warning: function is never used: `main2`, #[warn(dead_code)] on by default
src/main.rs:35 fn main2() {
src/main.rs:36 match log_time("log.txt") {
src/main.rs:37 Ok(..) => println!("File created!"),
src/main.rs:38 Err(e) => println!("Error: {}", e)
src/main.rs:39 }
src/main.rs:40 }
Running `target/debug/simple-log`
Listening on http://127.0.0.1:6767
Ctrl-C to shutdown server
複製程式碼
酷!這些未使用警告正是我所預期的,在瀏覽器上訪問 localhost:6767
仍然呈現“Hello World”頁面。
我們嘗試整合它們:
#[macro_use] extern crate nickel;
extern crate chrono;
use std::io::prelude::*;
use std::fs::{File,OpenOptions};
use std::io;
use chrono::{DateTime,Local};
use nickel::Nickel;
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
let mut file = try!(OpenOptions::new().
append(true).
write(true).
create(true).
open(filename));
try!(file.write_all(bytes));
Ok(())
}
fn log_time(filename: &'static str) -> io::Result<()> {
let entry = formatted_time_entry();
let bytes = entry.as_bytes();
try!(record_entry_in_log(filename, &bytes));
Ok(())
}
fn do_log_time() -> &'static str {
match log_time("log.txt") {
Ok(..) => println!("File created!"),
Err(e) => println!("Error: {}", e)
}
}
fn main() {
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time()
}
});
server.listen("127.0.0.1:6767");
}
複製程式碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:37:19: 37:44 error: mismatched types:
expected `&'static str`,
found `()`
(expected &-ptr,
found ()) [E0308]
src/main.rs:37 Ok(..) => println!("File created!"),
^~~~~~~~~~~~~~~~~~~~~~~~~
src/main.rs:38:19: 38:43 error: mismatched types:
expected `&'static str`,
found `()`
(expected &-ptr,
found ()) [E0308]
src/main.rs:38 Err(e) => println!("Error: {}", e)
^~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製程式碼
這裡的 println!
巨集功能是寫入標準輸出,但我是想要的是某些能返回字串的東西。這有 sprintln!
嗎,或者其他差不多的東西?
查了查資料,看起來答案是 format!
:
#[macro_use] extern crate nickel;
extern crate chrono;
use std::io::prelude::*;
use std::fs::{File,OpenOptions};
use std::io;
use chrono::{DateTime,Local};
use nickel::Nickel;
fn formatted_time_entry() -> String {
let local: DateTime<Local> = Local::now();
let formatted = local.format("%a, %b %d %Y %I:%M:%S %p\n").to_string();
formatted
}
fn record_entry_in_log(filename: &str, bytes: &[u8]) -> io::Result<()> {
let mut file = try!(OpenOptions::new().
append(true).
write(true).
create(true).
open(filename));
try!(file.write_all(bytes));
Ok(())
}
fn log_time(filename: &'static str) -> io::Result<()> {
let entry = formatted_time_entry();
let bytes = entry.as_bytes();
try!(record_entry_in_log(filename, &bytes));
Ok(())
}
fn do_log_time() -> &'static str {
match log_time("log.txt") {
Ok(..) => format!("File created!"),
Err(e) => format!("Error: {}", e)
}
}
fn main() {
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
do_log_time()
}
});
server.listen("127.0.0.1:6767");
}
複製程式碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:37:19: 37:43 error: mismatched types:
expected `&'static str`,
found `collections::string::String`
(expected &-ptr,
found struct `collections::string::String`) [E0308]
src/main.rs:37 Ok(..) => format!("File created!"),
^~~~~~~~~~~~~~~~~~~~~~~~
src/main.rs:38:19: 38:42 error: mismatched types:
expected `&'static str`,
found `collections::string::String`
(expected &-ptr,
found struct `collections::string::String`) [E0308]
src/main.rs:38 Err(e) => format!("Error: {}", e)
^~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製程式碼
因此,我知道從 String
轉化到 &str
的方法,嗯……我想起可以用 &
。
fn do_log_time() -> &'static str {
match log_time("log.txt") {
Ok(..) => &format!("File created!"),
Err(e) => &format!("Error: {}", e)
}
}
複製程式碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:37:20: 37:44 error: borrowed value does not live long enough
src/main.rs:37 Ok(..) => &format!("File created!"),
^~~~~~~~~~~~~~~~~~~~~~~~
note: reference must be valid for the static lifetime...
src/main.rs:37:19: 37:44 note: ...but borrowed value is only valid for the expression at 37:18
src/main.rs:37 Ok(..) => &format!("File created!"),
^~~~~~~~~~~~~~~~~~~~~~~~~
src/main.rs:38:20: 38:43 error: borrowed value does not live long enough
src/main.rs:38 Err(e) => &format!("Error: {}", e)
^~~~~~~~~~~~~~~~~~~~~~~
note: reference must be valid for the static lifetime...
src/main.rs:38:19: 38:43 note: ...but borrowed value is only valid for the expression at 38:18
src/main.rs:38 Err(e) => &format!("Error: {}", e)
^~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製程式碼
又出現了相同的錯誤。我想這裡我需要一個塊:
fn do_log_time() -> &'static str {
match log_time("log.txt") {
Ok(..) => {
let fmt = format!("File created!");
&fmt
},
Err(e) => {
let fmt = format!("Error: {}", e);
&fmt
}
}
}
複製程式碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:39:14: 39:17 error: `fmt` does not live long enough
src/main.rs:39 &fmt
^~~
note: reference must be valid for the static lifetime...
src/main.rs:38:48: 40:10 note: ...but borrowed value is only valid for the block suffix following s
tatement 0 at 38:47
src/main.rs:38 let fmt = format!("File created!");
src/main.rs:39 &fmt
src/main.rs:40 },
src/main.rs:43:14: 43:17 error: `fmt` does not live long enough
src/main.rs:43 &fmt
^~~
note: reference must be valid for the static lifetime...
src/main.rs:42:47: 44:10 note: ...but borrowed value is only valid for the block suffix following s
tatement 0 at 42:46
src/main.rs:42 let fmt = format!("Error: {}", e);
src/main.rs:43 &fmt
src/main.rs:44 }
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製程式碼
這仍然無效。我想問題出在 fmt
,fmt
只存在於新塊中,但是作為返回值,它需要在能在外面被使用。如果我在函式頂部宣告 fmt
會發生什麼呢?
fn do_log_time() -> &'static str {
let mut fmt = "".to_string();
match log_time("log.txt") {
Ok(..) => {
fmt = format!("File created!");
&fmt
},
Err(e) => {
fmt = format!("Error: {}", e);
&fmt
}
}
}
複製程式碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:40:14: 40:17 error: `fmt` does not live long enough
src/main.rs:40 &fmt
^~~
note: reference must be valid for the static lifetime...
src/main.rs:36:34: 48:2 note: ...but borrowed value is only valid for the block suffix following st
atement 0 at 36:33
src/main.rs:36 let mut fmt = "".to_string();
src/main.rs:37 match log_time("log.txt") {
src/main.rs:38 Ok(..) => {
src/main.rs:39 fmt = format!("File created!");
src/main.rs:40 &fmt
src/main.rs:41 },
...
src/main.rs:44:14: 44:17 error: `fmt` does not live long enough
src/main.rs:44 &fmt
^~~
note: reference must be valid for the static lifetime...
src/main.rs:36:34: 48:2 note: ...but borrowed value is only valid for the block suffix following st
atement 0 at 36:33
src/main.rs:36 let mut fmt = "".to_string();
src/main.rs:37 match log_time("log.txt") {
src/main.rs:38 Ok(..) => {
src/main.rs:39 fmt = format!("File created!");
src/main.rs:40 &fmt
src/main.rs:41 },
...
error: aborting due to 2 previous errors
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製程式碼
我不知道如何修正它。我現在打算放一放,一會再回來肝。
—
我嘗試了一些新方法,但是無一有效。我想我需要深入學習所有權和生命週期的工作機制。
我剛要查閱 Rust 文件時,我注意到了這個貼士:
我們選擇
String
而非&str
為其命名,通常來說,與一個擁有資料的型別打交道要比引用型別容易些。
因為我現在是在實踐而非理論學習,我想嘗試一下使用 String
看看是否有效。
現在:
fn do_log_time() -> String {
match log_time("log.txt") {
Ok(..) => format!("File created!"),
Err(e) => format!("Error: {}", e)
}
}
複製程式碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
Running `target/debug/simple-log`
Listening on http://127.0.0.1:6767
Ctrl-C to shutdown server
複製程式碼
有效!在瀏覽器訪問頁面顯示“File created!”,還寫了一個日誌檔案的條目。
我對它能工作並不感到驚訝 —— 我有一點理解使用 String
替代 &str
就能解決問題,但我想將此作為一個挑戰去弄清它。
現在我想通了,這是說得通的。我嘗試返回一個假借引用,但我同時擁有它,所以返回它沒有任何意義。那麼我如何在我自己的函式中返回 &str
呢?我沒有見過任何使用非假借“str
”的地方。
缺失了非假借 ~&str~ 型別,我只能認為它表現上是一個普通的 C 字串指標。這一定會引發一些我尚不瞭解的問題,對它來說要想很好的應用在 Rust 就必須與 Rust 互動,則 Rust 就必須相容共享所有權的規則。
如果程式的其他部分持有一個位元組陣列,提供我一個對該陣列的引用,這意味著什麼?&str
型別是不是基本上就像 C 字串一樣,可以被引用而沒有相關的額外後設資料?
Rust 文件提到從 &str
到 String
的轉化有一些成本。我不知道這是否真的如此,還是僅適用於靜態字串。在堆中分配 &str
需要複製 String
嗎?現在我明白了,我敢打賭答案是肯定的;如果你想把假借的值轉化成擁有的,唯一合理的辦法就是複製它。
無論如何,我都需要繼續深入。我覺得原因是,我想要做的事沒有意義,所以 Rust 正確的阻止了我。我希望我明白了,為什麼每一個 str
都是假借值。
我將嘗試讓 log_time
返回記錄時間,這樣可以顯示給使用者。我的首次嘗試:
fn log_time(filename: &'static str) -> io::Result<String> {
let entry = formatted_time_entry();
let bytes = entry.as_bytes();
try!(record_entry_in_log(filename, &bytes));
Ok(entry)
}
fn do_log_time() -> String {
match log_time("log.txt") {
Ok(entry) => format!("Entry Logged: {}", entry),
Err(e) => format!("Error: {}", e)
}
}
複製程式碼
=>
$ cargo run
Compiling simple-log v0.1.0 (file:///Users/joel/Projects/simple-log)
src/main.rs:32:8: 32:13 error: cannot move out of `entry` because it is borrowed
src/main.rs:32 Ok(entry)
^~~~~
src/main.rs:29:17: 29:22 note: borrow of `entry` occurs here
src/main.rs:29 let bytes = entry.as_bytes();
^~~~~
error: aborting due to previous error
Could not compile `simple-log`.
To learn more, run the command again with --verbose.
複製程式碼
嗯……我想這說得通。bytes
“借了” entry
的內容。當 OK(entry)
被呼叫時,這個值仍然被借用,這會導致錯誤。
現在它工作了:
fn log_time(filename: &'static str) -> io::Result<String> {
let entry = formatted_time_entry();
{
let bytes = entry.as_bytes();
try!(record_entry_in_log(filename, &bytes));
}
Ok(entry)
}
複製程式碼
=>
$ cargo run &
[1] 66858
$ Running `target/debug/simple-log`
Listening on http://127.0.0.1:6767
Ctrl-C to shutdown server
$ curl localhost:6767
Entry Logged: Tue, Jun 23 2015 12:34:19 AM
複製程式碼
這已經不是我第一次使用“貼一個新塊在這”這樣的特性了,但是它就是因此而工作了,這似乎是一個相當優雅的方式來處理這個問題。我首先想到的是,我需要呼叫另一個函式以某種方式將位元組“轉換”回 String
,但後來我意識到這實際上沒有意義,我需要以某種方式“釋放”借用。
我不明白錯誤資訊中“遷出 entry
”的意思。我覺得是隻要有假借引用,你就不能轉移值的所有權。但這也不一定是對的。把它傳給 Ok()
就是改變所有權了嗎?我對此很困惑,Rust 文件似乎並沒有針對這一具體的問題給出解釋,但我認為我的猜測就應該是對的 —— 所有權猜假借存在的時候不能被改變。我想是的。
我很欣慰我在 Rust 文件的假借部分中見到,使用塊是這個類問題的一種解決方案。
3 結語
整合工作比我預期的難得多。假借(Borrowing) / 所有權(Ownership)花費了我一些時間,所以我打算在這停一停,因為已經寫了很長了。
幸運的是,我認為我在慢慢理解 Rust 的工作機制,尤其是它的假借功能。這給了我對未來的希望。
—
系列文章:使用 Rust 開發一個簡單的 Web 應用
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。