Разработка простого веб-приложения на Rust, часть 3 — интеграция

сервер Программа перевода самородков Rust

Разработка простого веб-приложения на Rust, часть 3 — интеграция

1 Обзор прошлой ситуации

Это третья часть серии статей о разработке простого веб-приложения на Rust.

На данный момент у нас есть несколько MVP в нескольких исходных файлах Rust. Теперь мы хотим поместить их в приложение.

1.1 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)
    }
}

Теперь код веб-сервиса:

#[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

эффективный! На странице доступа браузера отображается сообщение «Файл создан!», а также записывается запись в файл журнала.

Я не удивлен, что это работает - я немного понимаю, используя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, кажется, не объясняет эту конкретную проблему, но я думаю, что моя догадка должна быть верной - догадки о праве собственности не могут быть изменены, пока они существуют. Я так думаю.

Я рад, что увидел в разделе guise документации Rust, что использование блоков является решением этого класса проблем.

3 Заключение

Интеграция оказалась намного сложнее, чем я ожидал. Заимствование/Владение заняло у меня некоторое время, поэтому я собираюсь остановиться здесь, так как это было написано уже давно.

К счастью, я думаю, что постепенно понимаю, как работает Rust, особенно его фичи. Это дает мне надежду на будущее.

Серия статей: Разработка простого веб-приложения на Rust


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.