[譯]使用 Rust 開發一個簡單的 Web 應用,第 3 部分 —— 整合

使用 Rust 開發一個簡單的 Web 應用,第 3 部分 —— 整合

1 前情回顧

這是使用 Rust 開發一個簡單的 Web 應用系列的第 3 部分.前端

到目前爲止,咱們已經有了一些最簡可行功能在幾個 Rust 源文件中。如今,咱們想把它們放在一個應用程序中。android

1.1 Review

咱們將如下兩個模塊整合在一塊兒:文件寫入 / 記錄代碼,Web 服務代碼。讓咱們 Review 一下它們:ios

首先,文件記錄代碼:git

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 服務代碼:github

#[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 函數名字改一下),看一看是否能成功編譯。web

#[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");
}
複製代碼

編譯運行:express

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

這仍然無效。我想問題出在 fmtfmt 只存在於新塊中,可是做爲返回值,它須要在能在外面被使用。若是我在函數頂部聲明 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 文檔提到從 &strString 的轉化有一些成本。我不知道這是否真的如此,仍是僅適用於靜態字符串。在堆中分配 &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 應用


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

相關文章
相關標籤/搜索