My presentations, using Reveal.js (mostly in Portuguese).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1028 lines
35 KiB

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Porque você deveria aprender Rust</title>
<meta name="description" content="Por que você deveria aprender Rust">
<meta name="author" content="Julio Biason">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
<link rel="stylesheet" href="reveal.js/css/reveal.css">
<link rel="stylesheet" href="reveal.js/css/theme/night.css" id="theme">
<!-- Code syntax highlighting -->
<link rel="stylesheet" href="reveal.js/lib/css/zenburn.css">
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
</script>
<!--[if lt IE 9]>
<script src="lib/js/html5shiv.js"></script>
<![endif]-->
<style type="text/css" media="screen">
.happy {
color: yellow;
}
.reveal section img {
border: none;
}
.reveal ul.empty {
list-style: none inside;
}
.revel ul.empty li {
display: block;
}
.cursor {
background-color: #666;
color: white;
}
img {
max-height: 90%;
}
td.seen {
font-style: italic;
font-weight: bold;
}
.semi-opaque {
background-color: rgba(0, 0, 0, 0.7);
}
</style>
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<section data-background="_images/rust-ferris.png" data-header>
<h2 class="semi-opaque">Porque Você Deveria Aprender Rust</h2>
</section>
<section data-background="_images/rust-ferris.png">
<h2 class="semi-opaque">Porque Você DEVE Aprender Rust</h2>
</section>
<aside class="notes">
Eu ia falar do porque o pessoal deveria aprender Rust,
mas acho que faz mais sentido dizer que o pessoal TEM
QUE aprender Rust.
</aside>
</section>
<section>
<section>
<img src="_images/avatar-20170726.png" alt="Me" style="float:left;width:200px;" class="no-border">
<div>
<ul class="empty">
<li>Júlio Biason</li>
<li><a href="https://functional.cafe/@juliobiason">https://functional.cafe/@juliobiason</a></li>
<li><a href="https://t.me/juliobiason">https://t.me/juliobiason</a></li>
<li>julio.biason@pm.me</li>
<li><a href="http://presentations.juliobiason.net">http://presentations.juliobiason.net</a></li>
</ul>
</div>
</section>
</section>
<section>
<section>
<h2>História</h2>
<ul>
<li>Criada em 2006 por Graydon Hoare.</li>
<li>Patrocinada pela Mozilla em 2009.</li>
<li>Versão 1.0 em 2015.</li>
<li>Versão atual: 1.37</li>
</ul>
</section>
<aside class="notes">
Parte burocrática da apresentação.
PS: Pode ser que, quando você essa apresentação, 1.37
não seja mais a versão atual; a cada 6 semanas, sai uma
nova versão do compilador.
</aside>
</section>
<section>
<section>
<h2>
História
<img src="_images/AYV1X0yv.png" alt="" style="width:100px;margin:0">
</h2>
<p>
Basic (com números e estruturado), dBase III Plus,
Clipper, Pascal, Cobol, Delphi (ObjectPascal),
C, C++, ActionScript (Flash), PHP, JavaScript,
Python, Objective-C, Clojure, Java, Scala
<strong>, Rust.</strong>
</p>
<aside class="notes">
Um porque de história sobre esse que lhes apresenta
a linguagem:
Eu já trabalhei com todas essas linguagens. Fora
essas, eu ainda sei ler
- Perl
- Ruby
- Haskell
- Swift
</aside>
</section>
<section>
<img src="_images/my_opinion.jpg" alt="">
<aside class="notes">
Alerta: Tudo aqui é a minha opinião sobre Rust e o
contexto geral de linguagens de programação.
</aside>
</section>
<section>
<div>
A language that doesn't affect the way you think
about programming, is not worth knowing.
</div>
<div>
-- Alan Perlis, "ALGOL"
</div>
<aside class="notes">
Apesar de ter todas essas linguagens, eu ainda
preciso passar essa frase do Perlis, porque
realmente Rust mudou a forma como eu penso em
outras linguagens.
Apesar do forte do Rust ser a proteção de memória,
eu posso oficialmente dizer que agora eu entendo
generics muito melhor por causa da forma como o
Rust trabalha.
</aside>
</section>
</section>
<section>
<section>
<h2>1. A Linguagem Mais Amada</h2>
<p>
<a href="https://insights.stackoverflow.com/survey/2019">
A linguagem mais amada segundo o StackOverflow
Survey 2019
</a>
<p class="fragment">... pelo 4⁰ ano seguido.</p>
<aside class="notes">
O resultado do StackOverflow é sobre qual
linguagem os programadores realmente gostam de
programar (e quais eles tem pavor de usar).
Pessoalmente, depois de 30 anos programando,
quando começei a brincar com Rust, eu
finalmente me diverti enquanto programava.
</aside>
</p>
</section>
</section>
<section>
<section>
<h2>2. "Low Level Language with High Level Abstractions"</h2>
</section>
<section>
<p>Resultado final com performance semelhante ao C...</p>
<img src="_images/rust-energy.png" alt="">
<aside class="notes">
Num estudo sobre quais linguagens consomem mais
energia, Rust chegou bem próximo de C.
Parte do trabalho de otimização do Rust vem da LLVM
(parte do pacote do Clang), mas a árvore de
abstração ainda é gerada pela linguagem -- o que
significa que o compilador Rust consegue "ajudar" o
LLVM a otimizar o código.
</aside>
</section>
<section>
<p>... mas com abstrações em algo nível</p>
<ul>
<li>Strings sem tamanho fixo</li>
<li>Listas</li>
<li>Mapas</li>
</ul>
<aside class="notes">
Ao contrário de C, em que só se mexe com ponteiros
pra cima e pra baixo, Rust tem todas as abstrações
de alto nível que estamos acostumados com outras
linguagens.
Honestamente, se surgir uma nova linguagem que
tenha mais proteções de memória, com performance
ainda melhor que C mas eu tiver que escrever uma
lista encadeada mais uma vez, eu destruo todas as
coisas tecnológicas ao meu redor...
E vou plantar batata.
... porque aprendemos absolutamente NADA sobre
desenvolvimento.
</aside>
</section>
</section>
<section>
<section>
<h2>3. Compilador Chato mas Amigável</h2>
</section>
<section>
<pre><code class="hljs rust" data-trim>
fn main() -&lt; int{
let a = 2;
a = 3;
println!("{}", a);
0
}
</code></pre>
<aside class="notes">
Primeiro contato com Rust: assim como C, tem uma
função `main`, que pode retornar um inteiro; para
atribuir variáveis, usa-se `let`.
Uma coisa: Rust é "strong and statically typed", o
que sigifnica que a linguagem tem tipos, mas por
padrão o compilador tenta inferir o tipo.
Uma outra forma de escrever o let seria:
```
let a: u8 = 2;
```
</aside>
</section>
<section>
<pre><code class="hljs" data-trim>
3 | let a = 2;
| -
| |
| first assignment to `a`
| help: make this binding mutable: `mut a`
4 | a = 3;
| ^^^^^ cannot assign twice to immutable variable
</code></pre>
<aside class="notes">
Se você tentar mudar um dado depois de criado, o
compilador Rust não vai deixar.
</aside>
</section>
<section data-transition="fade">
<pre><code class="hljs" data-trim data-line-numbers="7">
3 | let a = 2;
| -
| |
| first assignment to `a`
| help: make this binding mutable: `mut a`
4 | a = 3;
| ^^^^^ cannot assign twice to immutable variable
</code></pre>
<aside class="notes">
... mas se tu olhar com calma, tu vai ver que não só o
compilador disse, claramente, o que era o problema...
</aside>
</section>
<section data-transition="fade">
<pre><code class="hljs" data-trim data-line-numbers="5">
3 | let a = 2;
| -
| |
| first assignment to `a`
| help: make this binding mutable: `mut a`
4 | a = 3;
| ^^^^^ cannot assign twice to immutable variable
</code></pre>
<aside class="notes">
... como também vai dizer como resolver o problema.
</aside>
</section>
<section>
<img class="stretch" src="_images/Sorry-bout-that.gif" alt="">
<aside class="notes">
Ou seja, o compilador não só vai lá e diz: ERRADO!
... ele ainda dá uma dica de como resolver esse
problema.
</aside>
</section>
</section>
<section>
<section>
<h2>4. Borrow Checker</h2>
<aside class="notes">
O "Borrow Checker" é uma das principais novidades
do Rust em comparação com outras linguagens.
Ele basicamente controla como as variáveis vão ser
alocadas, quando serão desalocadas, quem pode
acessar o conteúdo da mesma e assim por diante.
</aside>
</section>
<section>
<pre><code class="hljs rust" data-trim>
a = String::from("hello");
</code></pre>
</section>
<section>
"Variável <code>a</code> tem o valor <code>"hello"</code>"
<aside class="notes">
Em todas as linguagens que eu usei, sempre que via
uma atribuição, eu pensava "a variável X tem o
valor Y" -- mesmo em C.
</aside>
</section>
<section>
<div>
"Posição de memória apontada por <code>a</code> tem o valor <code>"hello"</code>"
</div>
<div class="fragment">
<pre><code>
0x3f5cbf89 = "hello"
</code></pre>
</div>
<aside class="notes">
Nunca uma linguagem me fez "despensar" no nome da
variável pra pensar que ela representa, na verdade,
uma posição de memória.
</aside>
</section>
<section>
<img src="_images/rust-memory.png" alt="" class="stretch">
<aside class="notes">
É mais ou menos isso que Rust "pensa" internamente
quando vê uma variável: uma posição de memória, de
um tamanho já definido, de um tipo definido.
E essa posição de memória *pertence* apenas à
variável indicada.
</aside>
</section>
<section>
<pre><code class="hljs rust" data-trim>
fn main() {
let a = String::from("hello");
let _b = a;
println!("{}", a)
}
</code></pre>
</section>
<section>
<pre><code>
error[E0382]: borrow of moved value: `a`
--> src/main.rs:5:20
|
4 | let _b = a;
| - value moved here
5 | println!("{}", a)
| ^ value borrowed here after move
|
= note: move occurs because `a` has type
`std::string::String`, which does not
implement the `Copy` trait
</code></pre>
<aside class="notes">
O borrow checked não deixa a variável "a" ser
utilizada: quando a atribuímos "_b" o valor de "a",
o que estamos fazendo é indicando que aquela
posição de memória agora é controlada por "_b" e
não mais por "a".
</aside>
</section>
<section>
<p>E se eu precisar acessar a variável em mais de um lugar?</p>
<h3 class="fragment">References</h3>
<aside class="notes">
Assim como C++, Rust tem o conceito de "referências".
</aside>
</section>
<section>
<pre><code class="hljs rust" data-trim>
fn main() {
let a = String::from("hello");
let _b = &a;
println!("{}", a)
}
</code></pre>
</section>
<section>
<img src="_images/rust-reference.png" alt="" class="stretch">
<aside class="notes">
Uma referência nada mais é que um ponteiro para um
"controlador" de uma região de memória.
</aside>
</section>
<section>
<h3>Regras do Borrow Checker</h3>
<p class="fragment">
Uma região de memória tem apenas um dono.
</p>
<p class="fragment">
Passar um valor (região de memória) de uma variável
para outra, troca o dono.
</p>
<p class="fragment">
A região é desalocada quando o dono sair de escopo.
</p>
<aside class="notes">
Uma coisa engraçada sobre "quando sair de escopo" é
que existe uma função semelhante ao "free()" do C,
chamada "drop". Essa função não tem nada no corpo,
e recebe um parâmetro (sem ser por referência), se
tornando a dona da memória; assim, como ela termina
exatamente naquele ponto, a região de memória é
liberada.
</aside>
</section>
<section>
<h3>Regras do Borrow Checker</h3>
<p class="fragment">
Uma região de memória pode ter infinitas referências.
</p>
<p class="fragment">
... desde que elas não durem mais do que o dono.
</p>
<aside class="notes">
Não é possível ter uma função que cria uma variável
e retorna apenas uma referência para essa variável:
no momento que a função for encerrada, ela irá
levar todas as variáveis com ela e as referências
se tornaram inválidas.
</aside>
</section>
<section>
<h3>Regras do Borrow Checker</h3>
<p class="fragment">
É possível ter uma referência mutável de uma região de memória.
</p>
<p class="fragment">
... mas para haver uma referência mutável ela deve ser
a <strong>única</strong> referência.
</p>
</section>
<section>
<img src="_images/dunno.jpg" alt="" class="stretch">
<aside class="notes">
E o que isso ajuda, no final das contas?
</aside>
</section>
<section data-transition="fade">
<pre><code class="hljs go" data-trim>presente := Presente { ... }
canal &lt;- presente
&nbsp;</code></pre>
<aside class="notes">
Num exemplo em Go, criamos uma estrutura e passamos
essa estrutura para outra thread através de um
canal.
</aside>
</section>
<section data-transition="fade">
<pre><code class="hljs go" data-trim>presente := Presente { ... }
canal &lt;- presente
presente.abrir()</code></pre>
<aside class="notes">
... e depois de passar o presente pra outra pessoa,
nós abrimos o presente.
Mas se estamos entregando um presente pra alguém,
como é que estamos abrindo o presente?
O borrow checker não permite esse tipo de coisa:
Ele irá barrar a função atual de continuar
utilizando a variável porque, afinal de contas,
agora a região de memória pertence à outra função
(uma função que está rodando em outra thread).
</aside>
</section>
<section>
<a href="https://swift.org/blog/swift-5-exclusivity/">Swift 5 Exclusivity Enforcement</a>
<aside class="notes">
A ideia do borrow checker é tão interessante que
até o Swift 5 agora tem o seu próprio borrow
checker (com outro nome, mas o princípio da coisa é
basicamente o mesmo, apesar de ser um pouco mais
leve no Swift).
</aside>
</section>
</section>
<section>
<section>
<h3>Hora da anedota!</h3>
<img class="stretch" src="_images/senta-que-la-vem-historia.gif" alt="">
<p class="fragment"><code>localtime</code></p>
<p class="fragment"><code>SimpleDateFormatter</code></p>
<aside class="notes">
A muito tempo atrás, eu estava ajudando uma colega
a resolver um problema com processamento de eventos
num projeto em C. Aparentemente, quando um evento
era processado, acontecia do tempo de processamento
ficar errado (algo como ficar negativo ou levar
menos de 10ms pra fazer uma query num banco
oracle). Quando perguntei como ela estava
calculando o tempo, ela me falou que estava usando
o "localtime". Foi quando me lembrei que
"localtime" não é thread-safe e, por isso, quando
uma thread passava pela chamada da função, o valor
era "resetado".
Outra situação aconteceu recentemente: Num projeto
Java, começou a acontecer de, em alguns casos, a
função que convertia strings para Date começou a
dar resultados completamente errados.
Estranhamente, eu lembrei da questão do localtime e
perguntei se o projeto usava threads: sim; fui
direto no DuckDuckGo e procurei por
"simpledateformatter thread safe" e o primeiro
resultado foi uma pergunta do StackOverflow: "Why
isn't SimpleDateFormatter thread-safe?"
</aside>
</section>
<section>
<h3>Rust resolveria isso?</h3>
<h2 class="fragment">SIM</h2>
<h4 class="fragment">... porque nem ia compilar.</h4>
<aside class="notes">
Uma questão importante para o Rust são "Zero Cost
Abstractions", segundo a definição do Bjarne
Stroustrup, criado do C++: para que algo seja
aceito no compilador, é preciso que o custo de não
usar algo não acarrete nada; ou seja, tornar uma
função thread-safe simplesmente inserindo um mutex,
não é zero cost porque, se tu não estiver usando
threads, não faz sentido o mutex.
</aside>
</section>
</section>
<section>
<section>
<h2>5. Tipos Algébricos</h2>
</section>
<section>
<h3>enum</h3>
<pre><code class="hljs rust" data-trim>
enum IpAddr {
V4,
V6
}
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
enum IpAddr {
V4(String),
V6(String),
}
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
let home = IpAddr::V4(String::from("127.0.0.1"));
match home {
V4(address) =&gt; println!("IPv4 addr: {}", address),
V6(address) =&gt; println!("Ipv6 addr: {}", address),
}
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
enum Option&lt;T&gt; {
Some(T),
None
}
</code></pre>
</section>
</section>
<section>
<section>
<h2>6. Error Control</h2>
</section>
<section>
<pre><code class="hljs python" data-trim>
try:
something()
except Exception:
pass
</code></pre>
</section>
<section>
<pre><code class="hljs java" data-trim>
try {
something();
} catch (Exception ex) {
System.out.println(ex);
}
</code></pre>
</section>
<section>
<pre><code class="hljs c" data-trim>
FILE* f = fopen("someting.txt", "wb");
fprintf(f, "Done!");
fclose(f);
</code></pre>
</section>
<section>
<div>
Onde o erro foi tratado nisso?
</div>
</section>
<section>
<pre><code class="hljs rust" data-trim>
enum Result&lt;T, E&gt; {
Ok(T),
Err(E),
}
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
match File::create("something.txt") {
Ok(fp) =&gt; fp.write_all(b"Hello world"),
Err(err) =&gt; println!("Failure! {}", err),
}
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
match File::create("something.txt") {
Ok(fp) =&gt; match fp.write_all(b"Hello world") {
Ok(_) =&gt; (),
Err(err) =&gt; println!("Can't write! {}", err),
}
Err(err) =&gt; println!("Failure! {}", err),
}
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
let mut file = File::create("something.txt").unwrap();
file.write(b"Hello world").unwrap();
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
let mut file = File::create("something.txt")?;
file.write(b"Hello world")?;
OK(())
</code></pre>
</section>
</section>
<section>
<section>
<h2>7. Generics/Traits</h2>
</section>
<section>
<h3>Structs</h3>
<pre><code class="hljs rust" data-trim>
struct Gift {
package_color: String,
content: String
}
</code></pre>
<aside class="notes">
Structs em Rust são basicamente o mesmo que em C.
</aside>
</section>
<section>
<h3>Structs</h3>
<pre><code class="hljs rust" data-trim>
let presente = Gift { package_color: "red", content: "A GIFT!" };
</code></pre>
</section>
<section>
<h3>Structs Genéricas</h3>
<pre><code class="hljs rust" data-trim>
struct Point&lt;T&gt; {
x: T,
y: T
}
</code></pre>
</section>
<section>
<h3>Structs Genéricas</h3>
<pre><code class="hljs rust" data-trim>
let my_point = Point&lt;f32&gt;(x: 1.0, y: 2.0);
</code></pre>
</section>
<section>
<h3>Enums Generics</h3>
<pre><code class="hljs rust" data-trim>
enum Result&lt;T, E&gt; {
Ok(T),
Err(E),
}
</code></pre>
</section>
<section>
<h3>Traits</h3>
<pre><code class="hljs rust" data-trim>
trait Summary {
fn summarize(&amp;self) -&gt; String;
}
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
struct Phrase {
phrase: String
}
impl Summary for Phrase {
fn summarize(&amp;self) -&gt; String {
self.phrase
.split_whitespace()
.map(|word| word.chars().nth(0).unwrap())
.collect()
}
}
</code></pre>
</section>
<section>
<pre><code class="hljs rust" data-trim>
fn get_summary&lt;T&gt;(summarizable: T) -&gt; String
where T: Summary
{
...
}
</code></pre>
</section>
</section>
<section>
<section>
<h2>
<a href="https://doc.rust-lang.org/cargo/">
8. Cargo
</a>
</h2>
<p>"Cargo is the Rust package manager"</p>
<p>
"Cargo downloads your Rust package’s dependencies,
compiles your packages, makes distributable
packages, and uploads them to crates.io, the Rust
community’s package registry."
</p>
</section>
</section>
<section>
<section>
<h2>9. Tests</h2>
<pre><code class="hljs rust" data-trim>
#[cfg(test)]
mod tests {
#[test]
fn testing() {
}
}
</code></pre>
</section>
<section>
<pre><code>
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.22 secs
Running target/debug/deps/adder-ce99bcc2479f4607
running 1 test
test tests::testing ... ok
</code></pre>
</section>
</section>
<section>
<section>
<h2>10. Macros</h2>
<small class="fragment">?</small>
</section>
<section>
<h3>Log-Derive</h3>
<pre><code>
#[logfn(ok = "TRACE", err = "ERROR")]
fn call_isan(num: &amp;str) -&gt; Result&lt;Success, Error&gt; {
if num.len() &gt;= 10 &amp;&amp; num.len() &lt;= 15 {
Ok(Success)
} else {
Err(Error)
}
}
</code></pre>
</section>
</section>
<section>
<section>
<h2>11. Crazy stuff</h2>
</section>
<section>
<a href="https://medium.com/@shnatsel/how-rusts-standard-library-was-vulnerable-for-years-and-nobody-noticed-aebf0503c3d6">How Rust’s standard library was vulnerable for years and nobody noticed</a>
</section>
<section>
<a href="https://medium.com/@sgrif/no-the-problem-isnt-bad-coders-ed4347810270">No, the problem isn’t “bad coders”</a>
</section>
<section>
<img src="_images/rust-issues.png" alt="4.5k issues no Github" class="stretch">
</section>
<section>
<a href="https://rustup.rs/">rustup</a>
<div class="fragment">
<small>armv7-unknown-linux-gnueabihf</small>
</div>
<div class="fragment">
<small>wasm32-unknown-unknown</small> <small class="fragment">(WebAssembly)</small>
</div>
</section>
</section>
<section>
<h2>E agora?</h2>
<ul>
<li><a href="https://rustup.rs/">rustup</a></li>
<li><a href="https://doc.rust-lang.org/book/">The Rust Book</a></li>
<li><a href="https://doc.rust-lang.org/stable/rust-by-example/">Rust By Example</a></li>
<li><a href="https://play.rust-lang.org/?version=stable">Rust Playground</a></li>
<li><a href="https://t.me/rustlangbr">Rust Brasil (Telegram)</a></li>
</ul>
</section>
<section data-background='_images/thats-all-folks.jpg'>
<div class="semi-opaque">
<ul class="empty">
<li><a href="https://functional.cafe/@juliobiason">https://functional.cafe/@juliobiason</a></li>
<li><a href="https://t.me/juliobiason">https://t.me/juliobiason</a></li>
<li>julio.biason@pm.me</li>
<li><a href="http://presentations.juliobiason.net">http://presentations.juliobiason.net</a></li>
</ul>
</div>
</section>
</div>
</div>
<script src="reveal.js/lib/js/head.min.js"></script>
<script src="reveal.js/js/reveal.js"></script>
<script>
// Full list of configuration options available at:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
// showNotes: true,
transition: 'slide', // none/fade/slide/convex/concave/zoom
// Optional reveal.js plugins
dependencies: [
{ src: 'reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'reveal.js/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'reveal.js/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'reveal.js/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: 'reveal.js/plugin/zoom-js/zoom.js', async: true },
{ src: 'reveal.js/plugin/notes/notes.js', async: true }
]
});
</script>
</body>
</html>