summaryrefslogtreecommitdiff
path: root/why-rust.md
blob: b166f03399505345bbd6d40f1e92575d3fec5f03 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
---
title: "Why Rust?"
author: Lars Wirzenius
...

# About me

* First learnt programming in April, 1984, so 36 years ago
* Been programming more or less daily ever since
* BASIC, Pascal, C, a little C++, Unix shell, AWK, a lot of Python, Lisp, a
  little Haskell, a little Go, and now Rust
* I really like Rust
* Rust is like Haskell and C had a love child with unicorn fairy godmothers

---

# Why I like Rust: type system

* strong, static, versatile typing

  - helps avoid many bugs, reduces need to test every statement and
    branch with unit tests
  - not quite true that if a program compiles, it works, but Rust is a
    lot closer to that than C, C++, Pascal, etc
  - avoids entire classes of bugs: NULL pointer errors, unchecked
    error returns, implicit but wrong value conversions

* type inference: mostly only need to specify function signatures,
  structs, and similar parts and the compiler deduces the rest

---

# Why I like Rust: memory management

* manual, a la C: malloc, free

  - error prone: easy to leak (no free), easy to break (more than one
    free), hard to debug

* automatic, with garbage collection

  - Lisp, Python, Go
  - requires a runtime
  - despite decades of research, inevitably there is overhead and
    short pauses

* Rust: automatic with borrow checker, lifetimes

  - all heap values heap are tied to values on the stack
  - when on-stack value is freed, the heap value is dropped
  - requires making sure stack values have the right lifetimes
  - mutability must be declared explicitly
  - compiler can prove lack of data races

---

# Why I like Rust: misc

* not controlled by a megacorporation
* the ? operator and the Result type
* the Option type
* enums are powerful
* pattern matching is powerful and exhaustive
* no OOP, but traits
* zero cost abstractions actually work
* binaries execute quickly
* excellent concurrency support

---

# Why I like Rust: tooling and ecosystem

* cargo, crates.io work well and avoid most pitfalls other languages
  have
* good doctests
* things get better, the community and ecosystem seem lively
* avoids the 20/80 principle: problems are solved well, "pragmatic
  shortcuts" to allow implementation to be simpler are avoided, if the
  shortcut would be likely to bit users
* the community is friendly and constructive

---

# Not everything is perfect

* things in the language and important libraries change
  - but almost always with backwards compatibility)
* supports a lot fewer targets than C
  - but the big mainstream ones are supported
* not well packaged for Linux distros; rustup is needed
  - bug as the language stabilizes, this will improve
* de facto static linking only
  - dynamic linking works, but Rust ABI is not yet stable, so static
    is the default
* compilation is somewhat slow

---

# Example: hello

```{.rust .numberLines}
fn main() {
    println!("Hello, world!");
}
```

---

# Example: Subplot docgen main program

```{.rust .numberLines}
use chrono::{Local, TimeZone};
use pandoc;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::time::UNIX_EPOCH;
use structopt::StructOpt;

use subplot;
```
---

```{.rust .numberLines}
// Define the command line arguments.
#[derive(Debug, StructOpt)]
#[structopt(name = "docgen", 
            about = "Subplot document generator.")]
struct Opt {
    // One or more input filename.
    #[structopt(parse(from_os_str))]
    filenames: Vec<PathBuf>,

    // Set output file name.
    #[structopt(name = "FILE", long = "--output", 
      short = "-o", parse(from_os_str))]
    output: PathBuf,

    // Set date.
    #[structopt(name = "DATE", long = "--date")]
    date: Option<String>,
}
```
---

```{.rust .numberLines}
fn main() -> subplot::Result<()> {
    let opt = Opt::from_args();
    let mut pandoc = pandoc::new();

    // Tell Pandoc what the input files are.
    let first_file = &opt.filenames[0];
    for filename in opt.filenames.iter() {
        pandoc.add_input(&filename);
    }
```
---

```{.rust .numberLines}
    // Tell Pandoc what the input file format is.
    pandoc.set_input_format(
        pandoc::InputFormat::Markdown,
        vec![pandoc::MarkdownExtension::Citations],
    );

    // Add external Pandoc filters.
    let citeproc = std::path::Path::new("pandoc-citeproc");
    pandoc.add_option(
      pandoc::PandocOption::Filter(
        citeproc.to_path_buf()));

    // This would be nicer if pandoc took a Pathbuf for output,
    // instead of a String. Reported as
    // <https://github.com/oli-obk/rust-pandoc/issues/23>
    let output = format!("{}", opt.output.display());
    pandoc.set_output(pandoc::OutputKind::File(output));
```
---

```{.rust .numberLines}
    // Set options for Pandoc.
    pandoc.add_option(pandoc::PandocOption::TableOfContents);
    pandoc.add_option(pandoc::PandocOption::Standalone);
    pandoc.add_option(pandoc::PandocOption::NumberSections);
```
---

```{.rust .numberLines}
    // Metadata date from command line or file mtime. However, we
    // can't set it directly, since we don't want to override the date
    // in the actual document, if given, so we only set
    // user-provided-date. Our parsing code will use that if date is
    // not document metadata.
    let date = if let Some(date) = opt.date {
        date
    } else {
        // We have to parse the document to get the date from the
        // metadata. We can't set the metadata for typesetting until
        // we do. So we parse it twice.
        let doc = subplot::Document::from_file(&first_file)?;
        if let Some(date) = doc.meta().date() {
            date.to_string()
        } else {
            mtime(first_file)?
        }
    };
    pandoc.add_option(pandoc::PandocOption::Meta("date".to_string(), Some(date)));
```
---

```{.rust .numberLines}
    // Run Pandoc to parse the inputs into an abstract syntax tree,
    // then run our filter on that, then let Pandoc produce the output
    // file from the AST.
    pandoc.add_filter(|json| {
        let mut doc = subplot::Document::from_json(&json).expect("error parsing JSON AST");
        doc.has_title().expect("document has no title");
        doc.typeset();
        doc.ast().expect("error serializing into JSON AST")
    });
    pandoc.execute()?;

    Ok(())
}
```
---

```{.rust .numberLines}
fn mtime(filename: &Path) -> subplot::Result<String> {
    let mtime = fs::metadata(filename)?.modified()?;
    let secs = mtime.duration_since(UNIX_EPOCH)?.as_secs();
    let secs: i64 = format!("{}", secs).parse()?;
    let dt = Local.timestamp(secs, 0);
    Ok(dt.format("%Y-%m-%d %H:%M").to_string())
}
```