• A Simple Web App in Rust, Part 3 — Integration
  • Joel’s Journal
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: LeopPro
  • Proofread by: Ryouaki

Developing a simple Web application with Rust, Part 3 — Integration

1 Previously on

This is part 3 of a series on developing a simple Web application with Rust.

So far, we’ve had some minimal functionality in a few Rust source files. Now, we want to put them together in an application.

1.1 Review

We put together two modules: file write/record code and Web service code. Let’s Review them:

First, the file logging code:

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<()> {
    letmut 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) } }Copy the code

Now, the Web service code:

#[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"); }Copy the code

Integrating code: Fighting the type system

All right, I want to integrate these two programs. First, I’ll put them in a file (change the main name of one of them, of course) and see if they compile successfully.

#[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<()> {
    letmut 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");
}
Copy the code

Compile and run:

$ 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     letmut 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/ simle-log' Listening on http://127.0.0.1:6767 Ctrl-c to shutdown serverCopy the code

Cool! These unused warnings are exactly what I expected, and a browser visit to Localhost :6767 still displays a “Hello World” page.

We try to integrate them:

#[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<()> {
    letmut 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");
}
Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: they: 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.
Copy the code

The println here! The macro function is to write standard output, but I want something that returns a string. This has sprintln! Or something like that?

Check the information, it seems that the answer is 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<()> {
    letmut 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");
}
Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: they: 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.
Copy the code

So, I know how to convert from String to & STR, um… I think I can use &.

fn do_log_time() -> &'static str { match log_time("log.txt") { Ok(..) => &format! ("File created!" ), Err(e) => &format! ("Error: {}", e) } }Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: therefore: 37:44 error: borrowed value does not live long enough src/main.rs:37 Ok(..) => &format! ("File created!"),
                                  ^~~~~~~~~~~~~~~~~~~~~~~~
note: reference must be valid forthe static lifetime... src/main.rs:37:19: 37:44 note: ... but borrowed value is only validforthe 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 forthe static lifetime... src/main.rs:38:19: 38:43 note: ... but borrowed value is only validforthe 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.
Copy the code

The same mistake happened again. I think I need a block here:

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 } } }Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: house: her of the error: `fmt` does not live long enough src/main.rs:39 &fmt ^~~ note: reference must be validforthe static lifetime... src/main.rs:38:48: 40:10 note: ... but borrowed value is only validfor the block suffix following s
tatement 0 at 38:47
src/main.rs:38             letfmt = 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 forthe static lifetime... src/main.rs:42:47: 44:10 note: ... but borrowed value is only validfor the block suffix following s
tatement 0 at 42:46
src/main.rs:42             letfmt = 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.
Copy the code

This still doesn’t work. I think the problem is FMT. FMT only exists in the new block, but as a return value, it needs to be used outside of it. What happens if I declare FMT at the top of the function?

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 } } }Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: let them: the error are: `fmt` does not live long enough src/main.rs:40 &fmt ^~~ note: reference must be validforthe static lifetime... src/main.rs:36:34: 48:2 note: ... but borrowed value is only validfor 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 forthe static lifetime... src/main.rs:36:34: 48:2 note: ... but borrowed value is only validfor 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.
Copy the code

I don’t know how to fix it. I’m just going to let it go and come back to liver.

I tried some new methods, but nothing worked. I think I need to learn more about ownership and how the lifecycle works.

I was about to go through the Rust documentation when I noticed this tip:

We chose to name it String instead of &str; generally speaking, it’s easier to deal with a type that has data than a reference type.

Since I’m doing this in practice rather than theory, I want to try using String to see if it works.

Now:

fn do_log_time() -> String {
    match log_time("log.txt") { Ok(..) => format! ("File created!"), Err(e) => format! ("Error: {}", e)
    }
}
Copy the code

= >

$cargo run the 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 the serverCopy the code

Effective! “File Created!” is displayed on the browser page. And wrote an entry to the log file.

I’m not surprised it works — I kind of understand that using String instead of & STR would solve the problem, but I want to take this as a challenge to figure it out.

Now THAT I’ve figured it out, it makes sense. I tried to return a pseudo-reference, but I own it at the same time, so returning it doesn’t make any sense. So how do I return &str in my own function? I have not seen any use of a non-hypothetical “STR”.

Missing the non-pseudo-~ & STR ~ type, I can only assume that it behaves like a normal C string pointer. This certainly raises some questions that I don’t yet understand. For it to work well with Rust it must interact with Rust, and Rust must be compatible with rules of shared ownership.

What does it mean if some other part of the program holds a byte array and provides me with a reference to that array? Is the &str type basically like a C string that can be referenced without additional metadata associated with it?

The Rust documentation mentions that converting from & STR to String has some cost. I don’t know if this is true or if it only applies to static strings. Do I need to copy String to allocate &STR in the heap? Now THAT I see it, I bet the answer is yes; If you want to convert a fake value into a owned one, the only logical way to do that is to copy it.

In any case, I need to go further. I think the reason is that what I was trying to do didn’t make sense, so Rust was right to stop me. I hope I understand why each STR is a false value.

I will try to make log_time return the recorded time so that it can be displayed to the user. My first attempt:

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(entry) } fn do_log_time() -> String { match log_time("log.txt") { Ok(entry) => format! ("Entry Logged: {}", entry), Err(e) => format! ("Error: {}", e) } }
      Copy the code

= >

$cargo run the Compiling simple - log v0.1.0 (file:///Users/joel/Projects/simple-log) SRC/main. Rs: one: other lands the 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:29let bytes = entry.as_bytes();
                               ^~~~~
error: aborting due to previous error
Could not compile `simple-log`.

To learn more, run the command again with --verbose.
Copy the code

HMM… I guess that makes sense. Bytes Indicates the contents of the entry that is borrowed. This value is still borrowed when OK(Entry) is called, which results in an error.

Now it works:

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(entry) }
      Copy the code

= >

$cargo run & [1] 66858 $Running 'target/debug/ simle-log' Listening on http://127.0.0.1:6767 ctrl-c to shutdown the server  $ curl localhost:6767 Entry Logged: Tue, Jun 23 2015 12:34:19 AMCopy the code

This isn’t the first time I’ve used the “post a new block here” feature, but it just works, and it seems like a pretty elegant way to handle the problem. My first thought was that I needed to call another function to somehow “convert” the bytes back to String, but then I realized that this actually didn’t make sense and THAT I needed to “free” borrowing somehow.

I don’t understand the meaning of “move out entry” in the error message. Student: I think you can’t transfer the ownership of the value as long as there’s a fake reference. But that’s not necessarily true. Is passing it to Ok() a change of ownership? I’m puzzled by this, and the Rust documentation doesn’t seem to address this specific problem, but I think my guess is right — the ownership guess can’t be changed while it pretends to exist. I think so.

I’m glad TO see in the bogus section of Rust documentation that using blocks is one solution to this class of problems.

3 conclusion

Integration was much harder than I expected. The Borrowing/Ownership has taken me some time, so I’m going to stop here because I’ve been writing for a long time.

Fortunately, I think I’m slowly coming to understand how Rust works, especially its phoney features. It gives me hope for the future.

Article series: Developing a Simple Web application using Rust

  • Part 1
  • The second part a
  • The second part b
  • Part 3
  • Part 4
  • conclusion

The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.