Other
Ratatui
What is Ratatui?
- TUI = Terminal User Interface
- Ratatui
Ratatui Showcases
From Ratatui Apps
- atuin - Atuin replaces your existing shell history with a SQLite database, and records additional context for your commands.
- bandwhich - A CLI utility for displaying current network utilization by process, connection and remote IP/hostname.
- bottom - A customizable cross-platform graphical process/system monitor for the terminal.
- csvlens - A command line CSV file viewer. It is like less but made for CSV.
- dua - a disk space analysis tool designed for speed, leveraging parallel processing to quickly provide detailed disk usage information and allowing for faster deletion of unnecessary data compared to the standard ‘rm’ command.
- joshuto - Ranger-like terminal file manager written in Rust.
- fzf-make - A command line tool that executes make target using fuzzy finder with preview window.
- gitui - TUI for git written in rust.
- gpg-tui - gpg-tui is a Terminal User Interface for GnuPG.
- material - A material design color palette for the terminal.
- minesweep-rs - A mine sweeping game written in Rust.
- oatmeal - Oatmeal is a terminal UI chat application that speaks with LLMs.
- oha - oha sends some load to a web application and show realtime tui.
- rucola - An application to manage markdown notes from your terminal and compile them to HTML.
- scope-tui - A simple oscilloscope/vectorscope/spectroscope for your terminal.
- slumber - Terminal HTTP/REST client.
- taskwarrior-tui - A terminal user interface for taskwarrior.
- xplr - A hackable, minimal, fast TUI file explorer.
- yazi - Blazing fast terminal file manager written in Rust, based on async I/O.
- openapi-tui - Unlock the power of APIs with simplicity and speed, right from your terminal. View OpenAPI documentations in your terminal.
Ratatui - Hello World
- DefaultTerminal
- render_widget
- KeyEventKind}
- KeyCode
- Paragraph
- Event
Taken from the Hello world tutorial
- This example turns the terminal blue and writes some text on it.
- It waits till the user presses
q
and then quits.
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.28.1"
ratatui = "0.29.0"
use std::io; use ratatui::{ crossterm::event::{self, KeyCode, KeyEventKind}, style::Stylize, widgets::Paragraph, DefaultTerminal, }; fn main() -> io::Result<()> { let mut terminal = ratatui::init(); terminal.clear()?; let app_result = run(terminal); ratatui::restore(); app_result } fn run(mut terminal: DefaultTerminal) -> io::Result<()> { loop { terminal.draw(|frame| { let greeting = Paragraph::new("Hello Ratatui! (press 'q' to quit)") .white() .on_blue(); frame.render_widget(greeting, frame.area()); })?; if let event::Event::Key(key) = event::read()? { if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { return Ok(()); } } } }
Ratatui - handle keyboard events
-
KeyEventKind
-
KeyModifiers
-
CONTROL
-
SHIFT
-
ESC
-
We move the text to be displayed to a variable so we can change it.
-
We accept every key on the keyboard.
-
Pressing ESCape or Ctrl-C will terminate the application.
-
Any other key or key-combination will be displayed on the screen. (e.g. Left, Right)
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.28.1"
ratatui = "0.29.0"
use std::io; use crossterm::event::KeyModifiers; use ratatui::{ crossterm::event::{self, KeyCode, KeyEventKind}, style::Stylize, widgets::Paragraph, DefaultTerminal, }; fn main() -> io::Result<()> { let mut terminal = ratatui::init(); terminal.clear()?; let app_result = run(terminal); ratatui::restore(); app_result } fn run(mut terminal: DefaultTerminal) -> io::Result<()> { let mut text = String::new(); loop { terminal.draw(|frame| { let greeting = Paragraph::new(text.clone()).white().on_blue(); frame.render_widget(greeting, frame.area()); })?; if let event::Event::Key(key) = event::read()? { if key.kind == KeyEventKind::Press { // if key.code == KeyCode::Char('q') { // return Ok(()); // } //text.push_str("a"); if key.code == KeyCode::Esc { return Ok(()); } if key.modifiers == KeyModifiers::CONTROL && key.code == KeyCode::Char('c') { return Ok(()); } // // println!("{}", key.modifiers); // if key.modifiers == KeyModifiers::SHIFT { // text.push_str("Shift+"); // text.push_str(&key.code.to_string()); // } else { // text.push_str(&key.code.to_string()); // } //text.push_str(&key.code.to_string()); text = format!("{} - {}", key.modifiers, key.code); } } } }
Ratatui - a more structured way to handle state
- In this example we a struct, arbitrarily named
App
to repreent the state of the program. It will hold the text to be displayed and a flag indicating if we need to exit the program. - We also separated the
draw
and thehandle_key_event
methods and moved therun
method into the struct. - We also implemented the
Widget
. Later we'll use a lot more widgets to improve the look of the application.
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.28.1"
ratatui = "0.29.0"
use std::io; use crossterm::event::KeyModifiers; use ratatui::{ buffer::Buffer, crossterm::event::{self, KeyCode, KeyEventKind}, layout::Rect, style::Stylize, widgets::{Paragraph, Widget}, DefaultTerminal, Frame, }; #[derive(Debug, Default)] struct App { text: String, exit: bool, } impl App { fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { while !self.exit { terminal.draw(|frame| self.draw(frame))?; if let event::Event::Key(key) = event::read()? { self.handle_key_event(key); } } Ok(()) } fn draw(&self, frame: &mut Frame<'_>) { frame.render_widget(self, frame.area()); } fn handle_key_event(&mut self, key: event::KeyEvent) { if key.kind == KeyEventKind::Press { match key.code { KeyCode::Esc => self.exit = true, KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => self.exit = true, _ => self.text = format!("{} - {}", key.modifiers, key.code), } } } } impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { Paragraph::new(self.text.clone()) .white() .on_blue() .render(area, buf); } } fn main() -> io::Result<()> { let mut terminal = ratatui::init(); terminal.clear()?; let app_result = App::default().run(&mut terminal); ratatui::restore(); app_result }
Ratatui - counting keyboard events
- We have another field in the struct. A number that will be increased on ever press of the keyboard.
use std::io; use crossterm::event::KeyModifiers; use ratatui::{ buffer::Buffer, crossterm::event::{self, KeyCode, KeyEventKind}, layout::Rect, style::Stylize, widgets::{Paragraph, Widget}, DefaultTerminal, Frame, }; #[derive(Debug, Default)] struct App { counter: u32, text: String, exit: bool, } impl App { fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { while !self.exit { terminal.draw(|frame| self.draw(frame))?; if let event::Event::Key(key) = event::read()? { self.handle_key_event(key); } } Ok(()) } fn draw(&self, frame: &mut Frame<'_>) { frame.render_widget(self, frame.area()); } fn handle_key_event(&mut self, key: event::KeyEvent) { if key.kind == KeyEventKind::Press { match key.code { KeyCode::Esc => self.exit = true, KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => self.exit = true, _ => { self.text = format!("{} - {}", key.modifiers, key.code); self.counter += 1; } } } } } impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { Paragraph::new(format!("{}: {}", self.counter, self.text)) .white() .on_blue() .render(area, buf); } } fn main() -> io::Result<()> { let mut terminal = ratatui::init(); terminal.clear()?; let app_result = App::default().run(&mut terminal); ratatui::restore(); app_result }
Ratatui - a stopwatch or automatic counter - blocking vs. polling events
-
poll
-
At first it still counts every keyboard even as the reading from the keyboard is a blocking call.
-
We can use
event::poll
to check if there is anything to read at all. Thepoll
has a timeout. -
This way we can do work (count) in-between checking for events.
use std::io; use crossterm::event::KeyModifiers; use ratatui::{ buffer::Buffer, crossterm::event::{self, KeyCode, KeyEventKind}, layout::Rect, style::Stylize, widgets::{Paragraph, Widget}, DefaultTerminal, Frame, }; #[derive(Debug, Default)] struct App { counter: u32, text: String, exit: bool, } impl App { fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { while !self.exit { terminal.draw(|frame| self.draw(frame))?; // Blocking read if let event::Event::Key(key) = event::read()? { self.handle_key_event(key); } // Poll before blocking read // if event::poll(std::time::Duration::from_millis(100))? { // if let event::Event::Key(key) = event::read()? { // self.handle_key_event(key); // } // } // Short poll // if event::poll(std::time::Duration::from_millis(1))? { // if let event::Event::Key(key) = event::read()? { // self.handle_key_event(key); // } // } // std::thread::sleep(std::time::Duration::from_millis(100)); self.counter += 1; } Ok(()) } fn draw(&self, frame: &mut Frame<'_>) { frame.render_widget(self, frame.area()); } fn handle_key_event(&mut self, key: event::KeyEvent) { if key.kind == KeyEventKind::Press { match key.code { KeyCode::Esc => self.exit = true, KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => self.exit = true, _ => self.text = format!("{} - {}", key.modifiers, key.code), } } } } impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { Paragraph::new(format!("{}: {}", self.counter, self.text)) .white() .on_blue() .render(area, buf); } } fn main() -> io::Result<()> { let mut terminal = ratatui::init(); terminal.clear()?; let app_result = App::default().run(&mut terminal); ratatui::restore(); app_result }
Ratatui - counter
-
A copy of the counter app
-
Thre is a crash if the counter overflows or underflows!
[package]
name = "counter"
version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.28.1"
ratatui = "0.29.0"
use std::io; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ buffer::Buffer, layout::Rect, style::Stylize, symbols::border, text::{Line, Text}, widgets::{Block, Paragraph, Widget}, DefaultTerminal, Frame, }; #[derive(Debug, Default)] pub struct App { counter: u8, exit: bool, } impl App { pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { while !self.exit { terminal.draw(|frame| self.draw(frame))?; self.handle_events()?; } Ok(()) } fn draw(&self, frame: &mut Frame) { frame.render_widget(self, frame.area()); } fn handle_events(&mut self) -> io::Result<()> { match event::read()? { // it's important to check that the event is a key press event as // crossterm also emits key release and repeat events on Windows. Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { self.handle_key_event(key_event) } _ => {} }; Ok(()) } fn handle_key_event(&mut self, key_event: KeyEvent) { match key_event.code { KeyCode::Char('q') => self.exit(), KeyCode::Left => self.decrement_counter(), KeyCode::Right => self.increment_counter(), _ => {} } } fn exit(&mut self) { self.exit = true; } fn increment_counter(&mut self) { self.counter += 1; } fn decrement_counter(&mut self) { self.counter -= 1; } } impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { let title = Line::from(" Counter App Tutorial ".bold()); let instructions = Line::from(vec![ " Decrement ".into(), "<Left>".blue().bold(), " Increment ".into(), "<Right>".blue().bold(), " Quit ".into(), "<Q> ".blue().bold(), ]); let block = Block::bordered() .title(title.centered()) .title_bottom(instructions.centered()) .border_set(border::THICK); let counter_text = Text::from(vec![Line::from(vec![ "Value: ".into(), self.counter.to_string().yellow(), ])]); Paragraph::new(counter_text) .centered() .block(block) .render(area, buf); } } fn main() -> io::Result<()> { let mut terminal = ratatui::init(); let app_result = App::default().run(&mut terminal); ratatui::restore(); app_result } #[cfg(test)] mod tests { use super::*; use ratatui::style::Style; #[test] fn render() { let app = App::default(); let mut buf = Buffer::empty(Rect::new(0, 0, 50, 4)); app.render(buf.area, &mut buf); let mut expected = Buffer::with_lines(vec![ "┏━━━━━━━━━━━━━ Counter App Tutorial ━━━━━━━━━━━━━┓", "┃ Value: 0 ┃", "┃ ┃", "┗━ Decrement <Left> Increment <Right> Quit <Q> ━━┛", ]); let title_style = Style::new().bold(); let counter_style = Style::new().yellow(); let key_style = Style::new().blue().bold(); expected.set_style(Rect::new(14, 0, 22, 1), title_style); expected.set_style(Rect::new(28, 1, 1, 1), counter_style); expected.set_style(Rect::new(13, 3, 6, 1), key_style); expected.set_style(Rect::new(30, 3, 7, 1), key_style); expected.set_style(Rect::new(43, 3, 4, 1), key_style); assert_eq!(buf, expected); } #[test] fn handle_key_event() -> io::Result<()> { let mut app = App::default(); app.handle_key_event(KeyCode::Right.into()); assert_eq!(app.counter, 1); app.handle_key_event(KeyCode::Left.into()); assert_eq!(app.counter, 0); let mut app = App::default(); app.handle_key_event(KeyCode::Char('q').into()); assert!(app.exit); Ok(()) } }
TODO Ratatui - confirmation popup to verify intent to exit
TODO Ratatui - widgets
Show various widgets and how to work with them.
use std::io; use crossterm::event::KeyModifiers; use ratatui::{ buffer::Buffer, crossterm::event::{self, KeyCode, KeyEventKind}, layout::Rect, style::{Style, Stylize}, symbols::border, text::{Line, Text}, widgets::{Block, List, ListDirection, ListState, Paragraph, StatefulWidget, Widget}, DefaultTerminal, Frame, }; #[derive(Debug, Default)] struct App { counter: u8, exit: bool, } impl App { fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { while !self.exit { terminal.draw(|frame| self.draw(frame))?; if let event::Event::Key(key) = event::read()? { self.handle_key_event(key); } } Ok(()) } fn draw(&self, frame: &mut Frame<'_>) { frame.render_widget(self, frame.area()); } fn handle_key_event(&mut self, key: event::KeyEvent) { if key.kind == KeyEventKind::Press { match key.code { KeyCode::Esc => self.exit = true, KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => self.exit = true, KeyCode::Right => self.counter = self.counter.saturating_add(1), KeyCode::Left => self.counter = self.counter.saturating_sub(1), _ => {} } } } } impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { match self.counter { 1 => Paragraph::new(format!("{}", self.counter)) .white() .on_blue() .render(area, buf), 2 => Paragraph::new(format!("{}", self.counter)) .black() .on_green() .render(area, buf), 3 => { let text = Text::from(vec![Line::from(vec![ "Centered Value: ".to_string().black().on_white(), self.counter.to_string().red(), ])]); Paragraph::new(text) .centered() .on_light_blue() .render(area, buf); }, 4 => { let title = Line::from(" Counter App Tutorial ".bold()); let instructions = Line::from(vec![ " Decrement ".into(), "<Left>".blue().bold(), " Increment ".into(), "<Right>".blue().bold(), " Quit ".into(), "<Q> ".blue().bold(), ]); let block = Block::bordered() .title(title.centered()) .title_bottom(instructions.centered()) .border_set(border::THICK); let counter_text = Text::from(vec![Line::from(vec![ "Value: ".into(), self.counter.to_string().yellow(), ])]); Paragraph::new(counter_text) .centered() .block(block) .render(area, buf); }, 5 => { let items = ["Item 1", "Item 2", "Item 3"]; let list = List::new(items) .block(Block::bordered().title("List")) .style(Style::new().black()) .highlight_style(Style::new().italic()) .highlight_symbol(">>") .repeat_highlight_symbol(true) .direction(ListDirection::BottomToTop); Widget::render(list, area, buf); }, 0 => { let mut state = ListState::default(); let items = ["Item 1", "Item 2", "Item 3"]; let list = List::new(items) .block(Block::bordered().title("List")) .highlight_style(Style::new().reversed()) .highlight_symbol(">>") .repeat_highlight_symbol(true); StatefulWidget::render(list, area, buf, &mut state) } _ => Paragraph::new(format!("Not implemented: {} Press the left and right arrows to cycle throught the examples.", self.counter)) .white() .on_blue() .render(area, buf), } } } fn main() -> io::Result<()> { let mut terminal = ratatui::init(); terminal.clear()?; let app_result = App::default().run(&mut terminal); ratatui::restore(); app_result }
Tera
Tera Template system
Tera Hello World
- Context
- render
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[dependencies]
tera = "1.20.0"
use tera::Context; use tera::Tera; fn main() { let tera = Tera::new("templates/*.html").unwrap(); let mut context = Context::new(); context.insert("name", "World"); let result = tera.render("hello.html", &context).unwrap(); assert_eq!(result, "Hello, World!"); println!("{result}"); }
Hello, {{name}}!
$ tree
.
├── Cargo.lock
├── Cargo.toml
├── src
│ └── main.rs
└── templates
└── hello.html
Tera - list templates
I am not sure if this is ever needed in an application, but it helped me when first I could not figure out the expected directroy layout. It might be used for debugging to see if Tera can really find your templates.
$ tree
.
├── Cargo.lock
├── Cargo.toml
├── out.out
├── src
│ └── main.rs
└── templates
├── hello.html
└── incl
└── header.html
use tera::Tera; fn main() { let tera = Tera::new("templates/*.html").unwrap(); tera.get_template_names().for_each(|x| println!("{}", x)); }
incl/header.html
hello.html
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[dependencies]
tera = "1.20.0"
Hello, {{name}}!
<html>
<head>
</head>
Tera - use built-in filters on strings
use tera::Context; use tera::Tera; fn main() { let tera = Tera::new("templates/*.html").unwrap(); let mut context: Context = Context::new(); context.insert("text", " Hello, World! How are you? "); let result = tera.render("hello.html", &context).unwrap(); println!("{result}"); }
text: '{{text}}'
reverse: '{{text | reverse}}'
upper: '{{text | upper}}'
lower: '{{text | lower}}'
capitalize: '{{text | capitalize}}'
wordcount: '{{text | wordcount}}'
length: '{{text | length}}'
slugify: '{{text | slugify}}'
title: '{{text | title}}'
trim: '{{text | trim}}'
trim_start: '{{text | trim_start}}'
trim_end: '{{text | trim_end}}'
text: ' Hello, World! How are you? '
reverse: ' ?uoy era woH !dlroW ,olleH '
upper: ' HELLO, WORLD! HOW ARE YOU? '
lower: ' hello, world! how are you? '
capitalize: ' hello, world! how are you? '
wordcount: '5'
length: '28'
slugify: 'hello-world-how-are-you'
title: ' Hello, World! How Are You? '
trim: 'Hello, World! How are you?'
trim_start: 'Hello, World! How are you? '
trim_end: ' Hello, World! How are you?'
Tera - create your own filter
- register_filter
- Result
- Tera
- Value
- to_value
use std::collections::HashMap; use tera::{to_value, Context, Result, Tera, Value}; fn fixed_filter(_val: &Value, _map: &HashMap<String, Value>) -> Result<Value> { Ok(to_value("some fixed value").unwrap()) } fn same_value_filter(val: &Value, _map: &HashMap<String, Value>) -> Result<Value> { Ok(to_value(val).unwrap()) } fn my_len(val: &Value, _map: &HashMap<String, Value>) -> Result<Value> { let s = val.as_str().unwrap(); Ok(to_value(s.len()).unwrap()) } fn filter_with_params(_val: &Value, map: &HashMap<String, Value>) -> Result<Value> { //println!("map: {:?}", map); let param = map.get("param").unwrap().as_str().unwrap(); let attr = map.get("attr").unwrap().as_u64().unwrap(); Ok(to_value(format!("param={param} attr={attr}")).unwrap()) } fn main() { let mut tera = Tera::new("templates/*.html").unwrap(); tera.register_filter("fixed_filter", fixed_filter); tera.register_filter("same_value_filter", same_value_filter); tera.register_filter("my_len", my_len); tera.register_filter("filter_with_params", filter_with_params); let mut context = Context::new(); context.insert("text", "Hello, World!"); let result = tera.render("hello.html", &context).unwrap(); println!("{result}"); }
text: '{{text}}'
fixed_filter: '{{text | fixed_filter}}'
same_value_filter: '{{text | same_value_filter}}'
my_len: '{{text | my_len}}'
filter_with_params: '{{text | filter_with_params(attr=1, param="hello")}}'
text: 'Hello, World!'
fixed_filter: 'some fixed value'
same_value_filter: 'Hello, World!'
my_len: '13'
filter_with_params: 'param=hello attr=1'
Other
Variable shadowing
-
let
-
We can declare the same variable multiple time using the
let
keyword. -
The 2nd and subsequent declarations hide (shadow) the previous ones.
-
If this happened inside a block then when the shadowing version of the variable goes out of scope the previous value becomes visible again. (Observe, the last line being 6.)
fn main() { let x = 5; println!("x={x}"); let x = x + 1; println!("x={x}"); { println!("x={x}"); let x = x * 2; println!("x={x}"); } println!("x={x}"); }
x=5
x=6
x=6
x=12
x=6
String formatting
- format
- sprintf
fn main() { let name = "Foo"; let text1 = format!("Hello {}, how are you?", name); let text2 = format!("Hello {name}, how are you?"); println!("{}", text1); println!("{}", text2); }
fn main() { let text = "Hello"; println!("{}", text); let reversed = reverse(text); println!("{}", text); println!("{}", reversed); } fn reverse(text: &str) -> String { let reversed: String = text.chars().rev().collect(); reversed }
Hello
Hello
olleH
fn main() { let characters = ('a', 'b'); println!("{:?}", characters); let strings = ("a", "b"); println!("{:?}", strings); // println!("{:?}", letters.collect()); println!("Hello"); // println!('Hello'); // error: character literal may only contain one codepoint }
Factorial functions returning Result
- Restult
- Ok
- Err
fn main() { let numbers = vec![0, 1, 2, 3, 4, -1]; for number in numbers { let res = factorial(number); println!("{}! = {:?}", number, res); } println!(); //for number in numbers { // match factorial(number) { // Ok(res) => { // println!("{}! = {}", number, res); // }, // Err(err) => { // println!("Error {}", err); // } // } //} } fn factorial(number: i32) -> Result<i32, &'static str> { if number < 0 { return Err("Factorial requires a non-negative integer"); } let mut num = 1; for n in 1..=number { num *= n; } Ok(num) }
0! = Ok(1)
1! = Ok(1)
2! = Ok(2)
3! = Ok(6)
4! = Ok(24)
-1! = Err("Factorial requires a non-negative integer")
Split function into files
pub mod helper; fn main() { println!("main"); same_file(); helper::public_in_helper(); //helper::private_in_helper(); } fn same_file() { println!("same file"); }
#![allow(unused)] fn main() { pub fn public_in_helper() { println!("public_in_helper"); private_in_helper(); } fn private_in_helper() { println!("in_helper"); } }
[package]
name = "project"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
Variable Scope in Rust
- Every block creates a scope
fn main() { let fname = "Foo"; let lname = "Bar"; println!("{} {}", fname, lname); { let fname = "Peti"; println!("{} {}", fname, lname); } println!("{} {}", fname, lname); }
Foo Bar
Peti Bar
Foo Bar
Declare empty variable and assign to it later
fn main() { let r; { r = "one"; } // r = "two"; // cannot assign twice to immutable variable `r` println!("r: {}", r); }
Declare empty variable - requires type
#[allow(clippy::needless_late_init)] fn main() { let name; name = "Foo"; dbg!(name); }
[examples/intro/empty_string.rs:4] name = "Foo"
SystemTime now
//use std::time::Instant; use std::time::SystemTime; fn main() { //let now = Instant::now(); match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()), Err(_) => panic!("SystemTime before UNIX EPOCH!"), } //let now = SystemTime::now(); //println!("{:?}", now); }
Exit
use std::process; fn main() { let early_exit = false; if early_exit { println!("Early exit"); process::exit(3); } println!("Still running"); }
echo $?
echo %ERROR_LEVEL%
Define multiple variables
fn main() { let (a, b) = (2, "hello"); dbg!(a); dbg!(b); }
wc
[package]
name = "rust-wc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
use std::env; fn main() { // get the path from the command line // if it is a file work on it // if it is a folder ... // get command line parameters let files: Vec<String> = env::args().collect(); for file in &files[1..files.len()] { dbg!(file); } }
copy vs clone
TODO
Type alias
-
type
-
We can use the
type
keyword to create aliases to existing types. This can help us in reading the code, but Rust does not do any enforcement. -
As you can see in the following example we can pass arguments to a "different" type as long as it is an alias to the same type.
type Meters = u32; type Kilograms = u32; fn main() { let m: Meters = 3; let k: Kilograms = 4; calc(m); calc(k); } fn calc(y: Meters) { println!("{y}"); }
3
4
Solution: Age limit
use std::io; use std::io::Write; fn main() { let mut age = String::new(); print!("How old are you? "); io::stdout().flush().expect("Oups"); io::stdin() .read_line(&mut age) .expect("Failed to get input"); let age: f32 = age .trim() .parse() .unwrap_or_else(|_| panic!("Could not convert '{age}' to floating point number")); if age < 18.0 { println!("You are under 18. You cannot legally drink alcohol!"); } else { println!("Congratulations, you can legally drink alcohol!"); } }
use std::io; use std::io::Write; fn main() { let mut age = String::new(); print!("How old are you? "); io::stdout().flush().expect("Oups"); io::stdin() .read_line(&mut age) .expect("Failed to get input"); let age: f32 = age .trim() .parse() .unwrap_or_else(|_| panic!("Could not convert '{age}' to floating point number")); if age < 18.0 { println!("You are under 18. You cannot legally drink alcohol!"); } else if age >= 21.0 { println!("Congratulations, you can legally drink alcohol! Even in the USA."); } else { println!("You can drink alcohol, unless you are in the USA."); } }
use std::collections::HashMap; use std::io; use std::io::Write; fn main() { let mut age = String::new(); print!("How old are you? "); io::stdout().flush().expect("Oups"); io::stdin() .read_line(&mut age) .expect("Failed to get input"); let age: f32 = age .trim() .parse() .unwrap_or_else(|_| panic!("Could not convert '{age}' to floating point number")); let mut country = String::new(); print!("Which country are you in? "); io::stdout().flush().expect("Oups"); io::stdin() .read_line(&mut country) .expect("Failed to get input"); let country = country.trim(); let age_limit = HashMap::from([ ("Australia", 18.0), ("Austria", 16.0), // Actually it depends on the type of alcohol ("Hungary", 18.0), ("Israel", 18.0), ("Pakistan", 21.0), // Prohibited for muslims, 21 for others ("Paraguay", 20.0), ("USA", 21.0), ]); if age_limit.contains_key(&country) { if age_limit[&country] <= age { println!("Congratulations, you can legally drink alcohol in {country}."); } else { println!( "Sorry. You are under {}. You cannot legally drink alcohol in {}!", age_limit[country], country ); } } else { println!("You typed in '{country}'. Unfortunately we don't have that name in our database"); } }
Multi-counter in manually handled CSV file
use std::collections::HashMap; use std::env; use std::fs::File; use std::io::{BufRead, BufReader, Write}; use std::process::exit; type Counter = HashMap<String, u32>; const FILENAME: &str = "counter.csv"; fn main() { let name = get_name(); //println!("{}", name); let mut counters: Counter = read_the_csv_file(); if counters.contains_key(&name) { counters.insert(name.clone(), counters[&name] + 1); } else { counters.insert(name.clone(), 1); } // the above 5 lines could be replaced with this one: //*counters.entry(name.clone()).or_insert(0) += 1; println!("{}: {}", &name, counters[&name]); save_the_csv_file(counters) } /// Get name from command line fn get_name() -> String { let args: Vec<String> = env::args().collect(); if args.len() != 2 { eprintln!("Usage: {} NAME", args[0]); exit(1); } args[1].clone() } fn read_the_csv_file() -> Counter { let mut counters: Counter = HashMap::new(); match File::open(FILENAME) { Ok(file) => { let reader = BufReader::new(file); for line in reader.lines() { let line = line.unwrap(); let line = line.trim(); let parts: Vec<&str> = line.split('=').collect(); let name = parts[0]; let count: u32 = parts[1].parse().unwrap(); counters.insert(name.to_string(), count); } } Err(error) => { println!("Error opening file {}: {}", FILENAME, error); } } counters } fn save_the_csv_file(counters: Counter) { let mut file = File::create(FILENAME).unwrap(); for (name, count) in counters.iter() { writeln!(&mut file, "{}={}", name, count).unwrap(); } }
Get path to current executable
- current_exe
use std::env; fn main() { println!("{}", env::current_exe().unwrap().display()); match env::current_exe() { Ok(exe_path) => println!("Path: {}", exe_path.display()), Err(err) => println!("Failed to get current exe path: {err}"), }; }
cache dependencies with sccache
Commafy
[package]
name = "commafy"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
#![allow(unused)] fn main() { pub fn commafy<Integer: Into<i64> + Copy + std::fmt::Debug + std::fmt::Display>( num: Integer, ) -> String { //pub fn commafy(num: i32) -> String { let num = format!("{num}"); let mut ix = 0; let num = num .chars() .rev() .map(|chr| { ix += 1; if ix % 3 == 1 && ix > 1 { format!(",{chr}") } else { format!("{chr}") } }) .collect::<String>(); num.chars().rev().collect::<String>() } #[cfg(test)] mod tests { use super::commafy; #[test] fn test_commafy() { assert_eq!("1", commafy(1)); assert_eq!("12", commafy(12)); assert_eq!("123", commafy(123)); assert_eq!("1,234", commafy(1234)); assert_eq!("12,345", commafy(12345)); assert_eq!("123,456", commafy(123456)); assert_eq!("1,234,567", commafy(1234567)); assert_eq!("1,234", commafy(1234i16)); assert_eq!("254", commafy(254u8)); assert_eq!("1,254", commafy(1254u16)); } } }
Commafys
fn main() { assert_eq!(commafy("56789012"), "56,789,012"); assert_eq!(commafy("789012"), "789,012"); assert_eq!(commafy("123"), "123"); assert_eq!(commafy("1234"), "1,234"); } fn commafy(text: &str) -> String { let mut chars: Vec<_> = text.chars().collect(); let len = text.len(); for i in (3..text.len()).step_by(3) { chars.insert(len - i, ','); } let commafied: String = chars.iter().collect(); commafied }
Use statements
// Use one-by-one //#[allow(unused_imports)] //use std::io::Read; //#[allow(unused_imports)] //use std::io::Write; #[allow(unused_imports)] use std::fs::File; #[allow(unused_imports)] use std::io; // Combine two //#[allow(unused_imports)] //use std::io::{Read, Write}; // Combine crate and function //#[allow(unused_imports)] //use std::io::{self, Write}; // Combine several, unrelated //#[allow(unused_imports)] //use { // std::io::Read, // std::io::Write, // std::fs::File, // std::io, //}; fn main() { println!("Hello, world!"); }
Take version number from Cargo.toml
- VERSION
- CARGO_PKG_VERSION
const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { println!("Running version {VERSION}"); }
Ansi colors
[package]
name = "ansi-colors"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ansi_term = "0.12.1"
use ansi_term::Colour::{Blue, Green, Red}; fn main() { println!("Hello, world!"); println!("{}", Blue.bold().paint("blue")); println!("{}", Green.dimmed().paint("green")); println!( "Hello {} sky and {} grass!", Blue.bold().paint("blue"), Green.bold().paint("green") ); println!("{}", Red.bold().paint("red")); }
What I learned from learning Rust
-
Gabor Szabo
-
https://szabgab.com/
-
https://github.com/szabgab
-
https://rustatic.code-maven.com/
-
https://rust-digger.code-maven.com/
-
https://rust.code-maven.com/
Temperature converter
[package]
name = "temperature-converter"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
{% embed include file="src/examples/other/temperature-converter/input.json)
// // Accept the input filename as a parameter // // Read JSON file into a struct // // Convert to andother struct // // Print // enum Scale { // Centigrade, // Farenheit, // Kelvin, // } // struct TemperatureInput { // input_temperature: f32, // input_scale: Scale, // output_scale: Scale, // } // fn main() { // calc(); // } // fn calc() -> Result<String, String> { // let filename = String::from("input.json"); // let fh = std::fs::File::open(filename)?; // println!("Hello, world!"); // () // } fn main() { //fibonacci(10); } //fn fibo() { //}
Check slides
use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader}; use std::iter::FromIterator; use std::path::Path; use std::path::PathBuf; use std::process::exit; use std::process::Command; use std::sync::mpsc; use chrono::{DateTime, Utc}; use clap::Parser; use threadpool::ThreadPool; // use regex::Regex; // from all the md files extract the list of included files // from the examples/ directory list all the files // make sure each file is included and is only included once // Run every rs file, if there is an out file compare the results. // const ROOT: &str = "../../.."; const ACTIONS: [&str; 6] = ["update", "fmt", "fmt_check", "clippy", "test", "run"]; #[derive(Parser)] struct Cli { #[arg(long, help = "Print debug information")] verbose: bool, #[arg(long, help = "Cleanup the target directory")] cleanup: bool, #[arg(long, help = "Check if all the examples are used in the md files")] use_examples: bool, #[arg(long)] update: bool, #[arg(long)] fmt: bool, #[arg(long)] fmt_check: bool, #[arg(long)] clippy: bool, #[arg(long)] test: bool, #[arg(long)] run: bool, #[arg(long, help = "Run all the checks on the selected examples")] check: bool, #[arg(help = "List of examples to run. e.g examples/other/check-slides/")] examples: Vec<String>, } fn get_actions(args: &Cli) -> Vec<&str> { let mut actions: Vec<&str> = vec![]; if args.update { actions.push("update"); } if args.fmt { actions.push("fmt") } if args.check || args.fmt_check { actions.push("fmt_check") } if args.check || args.clippy { actions.push("clippy") } if args.check || args.test { actions.push("test") } if args.check || args.run { actions.push("run") } actions } fn main() { let start: DateTime<Utc> = Utc::now(); let args = Cli::parse(); let log_level = if args.verbose { log::Level::Info } else { log::Level::Warn }; simple_logger::init_with_level(log_level).unwrap(); log::info!("verbose: {}", args.verbose); std::env::set_current_dir(ROOT).unwrap(); let examples = if args.examples.is_empty() { get_crates(Path::new("examples")) } else { args.examples.iter().map(PathBuf::from).collect() }; log::info!("Number of examples: {}", examples.len()); let unused_examples = check_use_of_example_files(args.use_examples); let mut success: HashMap<String, i32> = HashMap::new(); let mut failures: HashMap<String, Vec<PathBuf>> = HashMap::new(); let actions = get_actions(&args); cargo_on_all( &mut success, &mut failures, &examples, actions.iter().map(|x| x.to_string()).collect(), args.cleanup, ); let mut failures_total = 0; for action in ACTIONS { if success.contains_key(action) && failures.contains_key(action) { log::info!( "{action} success: {}, failure: {}", success[action], failures[action].len() ); failures_total += failures[action].len(); } } println!("------- Report -------"); let end: DateTime<Utc> = Utc::now(); println!("Elapsed: {}", end.timestamp() - start.timestamp()); if !unused_examples.is_empty() { println!("There are {} unused examples", unused_examples.len()); for example in &unused_examples { println!(" {example:?}",); } } for action in ACTIONS { if failures.contains_key(action) { report_errors(action, &failures[action]); } } if !unused_examples.is_empty() || failures_total > 0 { exit(1); } } fn report_errors(name: &str, failures: &[PathBuf]) { if !failures.is_empty() { println!("There are {} examples with {name} errors.", failures.len()); for failure in failures { println!(" {failure:?}",); } } } fn check_use_of_example_files(use_examples: bool) -> Vec<String> { let mut unused_examples = vec![]; if !use_examples { return unused_examples; } log::info!("check_use_of_example_files"); let md_files = get_md_files(); let imported_files = get_imported_files(md_files); let examples = get_all_the_examples(); for filename in examples { if filename.ends_with("swp") { continue; } if filename.ends_with("counter.db") { continue; } if !imported_files.contains(&filename) { if filename.starts_with("examples/rocket/people-and-groups/templates/") { continue; } if filename.starts_with("examples/surrealdb/embedded-rocksdb/tempdb/") { continue; } if filename.starts_with("examples/surrealdb/cli-multi-counter/db/") { continue; } let files = [ "examples/threads/count-characters/aadef.txt", "examples/threads/count-characters/abc.txt", "examples/rocket/static-files/static/favicon.ico", "examples/files/count-digits/digits.txt", "examples/files/read-whole-file/data.txt", "examples/ownership/concatenate-content-of-files/dog.txt", "examples/ownership/concatenate-content-of-files/cat.txt", "examples/ownership/read-file-and-trim-newline/cat.txt", "examples/other/check-slides/skips.csv", ] .into_iter() .map(|name| name.to_owned()) .collect::<Vec<String>>(); if files.contains(&filename) { continue; } log::error!("Unused file: `{filename}`"); unused_examples.push(filename); } } unused_examples } fn cargo_on_all( success: &mut HashMap<String, i32>, failures: &mut HashMap<String, Vec<PathBuf>>, crates: &[PathBuf], actions: Vec<String>, cleanup: bool, ) { log::info!("cargo_on_all {actions:?} START"); let start: DateTime<Utc> = Utc::now(); let number_of_crates = crates.len(); let (tx, rx) = mpsc::channel(); let max_threads = 2; let pool = ThreadPool::new(max_threads); for (ix, crate_folder) in crates.iter().cloned().enumerate() { log::info!("crate: {}/{number_of_crates}, {crate_folder:?}", ix + 1); let mytx = tx.clone(); let actions = actions.clone(); pool.execute(move || { let res = cargo_actions_on_single(&crate_folder, &actions, cleanup, ix + 1, number_of_crates); log::debug!("sending res {res:?}"); mytx.send((res, crate_folder)).unwrap(); }); } drop(tx); // close this channel to allow the last thread to finish the receiving loop for received in rx { for action in &actions { if received.0[action] { log::debug!("success {action}"); *success.entry(action.clone()).or_insert(0) += 1; } else { log::debug!("received failures {:?}", received.1.clone()); failures .entry(action.clone()) .or_default() .push(received.1.clone()); log::debug!("all failures {:?}", failures); } } } let end: DateTime<Utc> = Utc::now(); log::info!( "cargo_on_all {actions:?} DONE Elapsed: {}", end.timestamp() - start.timestamp() ); } fn cargo_actions_on_single( crate_folder: &PathBuf, actions: &[String], cleanup: bool, ix: usize, total: usize, ) -> HashMap<String, bool> { log::info!("Actions on {crate_folder:?} START {ix}/{total}"); let start = Utc::now(); let mut res = HashMap::new(); for action in actions { res.insert(action.clone(), cargo_on_single(crate_folder, action)); } if cleanup { std::fs::remove_dir_all(crate_folder.join("target")).unwrap(); } let end = Utc::now(); log::info!( "Actions on {crate_folder:?} DONE {ix}/{total} Elapsed: {}", end.timestamp() - start.timestamp() ); res } fn cargo_on_single(crate_path: &PathBuf, action: &str) -> bool { log::info!("{action} on {crate_path:?} START"); let args = get_args(action); let skip = skip(action); let folder = crate_path.clone().into_os_string().into_string().unwrap(); let folders = skip.iter().map(|x| x.to_string()).collect::<String>(); if folders.contains(&folder) { log::info!("{action} on {crate_path:?} SKIPPED"); return true; } let error = format!("failed to execute 'cargo {args:?} --check' process"); let mut cmd = Command::new("cargo"); for arg in args { cmd.arg(arg); } let result = cmd.current_dir(crate_path).output().expect(&error); log::info!("{action} on {crate_path:?} DONE"); if !result.status.success() { let code = result.status.code().unwrap(); log::error!("Cannot execute {args:?} on crate: {crate_path:?} exit code: {code} stdout: {} stderr: {}", std::str::from_utf8(&result.stdout).unwrap(), std::str::from_utf8(&result.stderr).unwrap()); return false; } true } fn get_crates(path: &Path) -> Vec<PathBuf> { log::info!("get_crates"); let crates = get_crates_recoursive(path); log::info!("get_crates done\n"); crates } fn get_crates_recoursive(path: &Path) -> Vec<PathBuf> { let mut crates: Vec<PathBuf> = vec![]; for entry in path.read_dir().expect("read_dir call failed").flatten() { if entry.path().ends_with("target") { continue; } //println!("{:?}", entry); if entry.path().ends_with("Cargo.toml") { //println!("cargo: {:?}", entry.path().parent()); crates.push(entry.path().parent().unwrap().to_path_buf()); } if entry.path().is_dir() { crates.extend(get_crates_recoursive(entry.path().as_path())); } } crates } // TODO: go deeper than 2 levels to also handle examples/*/src/main.rs // TODO: but exclude examples/*/target/ // TODO: move the exclude lists to external files fn get_all_the_examples() -> Vec<String> { log::info!("get_all_the_examples"); let exclude: Vec<String> = [ "examples/image/create-image/image.png", "examples/other/multi_counter_with_manual_csv/counter.csv", "examples/other/send-mail-with-sendgrid/config.txt", ] .iter() .map(|path| path.to_string()) .collect(); let pathes = get_examples(Path::new("examples")); let pathes: Vec<String> = pathes .iter() .filter(|path| !exclude.contains(path)) .cloned() .collect(); log::info!("get_all_the_examples done\n"); pathes } fn get_examples(path: &Path) -> Vec<String> { let mut examples: Vec<String> = vec![]; for entry in path.read_dir().expect("read_dir call failed").flatten() { if entry.path().ends_with("Cargo.lock") { continue; } if entry.path().ends_with("Cargo.toml") { continue; } if entry.path().is_dir() { if entry.path().ends_with("target") { continue; } examples.extend(get_examples(entry.path().as_path())); continue; } //dbg!(&entry); if entry.path().is_file() { examples.push(entry.path().into_os_string().into_string().unwrap()); continue; } } examples //return Vec::from_iter( examples.iter().map(|s| s.clone().into_os_string().into_string().expect("Bad") ) ); } fn get_imported_files(md_files: Vec<PathBuf>) -> Vec<String> { log::info!("get_imported_files"); // println!("{:?}", md_files); //  // let re = Regex::new(r"^!\[\]]\((.*)\)\s*$").unwrap(); let mut imported_files = vec![]; for filename in md_files { //println!("{:?}", filename); match File::open(filename.as_path()) { Ok(file) => { let reader = BufReader::new(file); for line in reader.lines() { let line = line.unwrap(); if line.starts_with(" && line.ends_with(')') { //println!("{}", &line[4..line.len()-1]) imported_files.push((line[4..line.len() - 1]).to_string()); } } } Err(error) => { log::error!("Failed opening file {filename:?}: {error}"); } } } log::info!("get_imported_files done\n"); Vec::from_iter(imported_files.iter().map(|s| s.to_string())) } fn get_md_files() -> Vec<PathBuf> { log::info!("get_md_files"); let mut md_files = vec![]; let path = Path::new("."); for entry in path.read_dir().expect("read_dir call failed").flatten() { let filename = entry.path(); //println!("{:?}", filename); //.as_path()); let extension = filename.extension(); if let Some(value) = extension { if value == "md" { // println!("{:?}", filename); //println!("{}", filename); md_files.push(filename); } } //println!("{:?}", extension.unwrap()) } log::info!("get_md_files done\n"); md_files } #[derive(Debug, serde::Deserialize, serde::Serialize)] struct Skip { example: String, update: String, test: String, clippy: String, run: String, comment: String, } // TODO read this file only once // TODO compute the path to the file in a simpler way // TODO change the lookup to be an O(1) operation (instead of returning a vector return a hashmap) // TODO verify that every example listed in the csv file is still present in the examples directory // TODO mark examples that should fail compilation and then check that they do fail fn read_skips() -> HashMap<String, Vec<String>> { let path = std::env::current_exe().unwrap(); let path = path .parent() .unwrap() .parent() .unwrap() .parent() .unwrap() .join("skips.csv"); let csv_text = std::fs::read_to_string(path).unwrap(); let mut skips: HashMap<String, Vec<String>> = HashMap::from([ (String::from("update"), vec![]), (String::from("test"), vec![]), (String::from("clippy"), vec![]), (String::from("run"), vec![]), ]); let mut rdr = csv::ReaderBuilder::new() .has_headers(true) .trim(csv::Trim::All) .from_reader(csv_text.as_bytes()); for result in rdr.deserialize::<Skip>() { match result { Ok(record) => { if record.update == "true" { skips .get_mut("update") .unwrap() .push(record.example.clone()); } if record.test == "true" { skips.get_mut("test").unwrap().push(record.example.clone()); } if record.clippy == "true" { skips .get_mut("clippy") .unwrap() .push(record.example.clone()); } if record.run == "true" { skips.get_mut("run").unwrap().push(record.example); } } Err(err) => panic!("Error parsing csv {err}"), } } skips } fn skip(name: &str) -> Vec<String> { let skips = read_skips(); if name == "update" { return skips[&String::from("update")].clone(); } if name == "clippy" { return skips[&String::from("clippy")].clone(); } if name == "run" { return skips[&String::from("run")].clone(); } if name == "test" { return skips[&String::from("test")].clone(); } vec![] } fn get_args(action: &str) -> &'static [&'static str] { if action == "clippy" { return &["clippy", "--", "--deny", "warnings"]; } if action == "update" { return &["update"]; } if action == "fmt" { return &["fmt"]; } if action == "fmt_check" { return &["fmt", "--check"]; } if action == "test" { return &["test"]; } if action == "run" { return &["run"]; } panic!("Unknown action: {action}"); }
Expressions vs statements
- Expressions have a return value do NOT need a trailing semi-colon
- Statements do not have values and need a semi-colon
fn main() { let x = 2; println!("{x}"); let y = { let a = 2; let b = 3; a + b // no semi-colon here! }; // there is a semi-colon here! println!("{y}"); }
Send email via SendGrid
use sendgrid::SGClient; use sendgrid::{Destination, Mail}; use std::fs::File; use std::io::{BufRead, BufReader}; fn main() { let sendgrid_api_key = get_key(); let to_address = String::from("szabgab@gmail.com"); let to_name = String::from("Gabor Szabo"); let subject = String::from("Test mail"); sendgrid(&sendgrid_api_key, &to_name, &to_address, &subject); } fn get_key() -> String { let filename = "config.txt"; match File::open(filename) { Ok(file) => { let reader = BufReader::new(file); for line in reader.lines() { let line = line.unwrap(); let parts = line.split('='); let parts: Vec<&str> = parts.collect(); if parts[0] == "SENDGRID_API_KEY" { return parts[1].to_owned(); } } panic!("Could not find line"); } Err(error) => { panic!("Error opening file {}: {}", filename, error); } } } fn sendgrid(api_key: &str, to_name: &str, to_address: &str, subject: &str) { let sg = SGClient::new(api_key); let mut x_smtpapi = String::new(); x_smtpapi.push_str(r#"{"unique_args":{"test":7}}"#); let mail_info = Mail::new() .add_to(Destination { address: to_address, name: to_name, }) .add_from("gabor@szabgab.com") .add_from_name("Original Sender") .add_subject(subject) .add_html("<h1>Hello from SendGrid!</h1>") .add_header(String::from("x-cool"), "indeed") .add_x_smtpapi(&x_smtpapi); match sg.send(mail_info) { Err(err) => println!("Error: {}", err), Ok(body) => println!("Response: {:?}", body), }; }
[package]
name = "send-mail-with-sendgrid"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sendgrid = "0.19"
With a file called config.txt in the same directy that has:
SENDGRID_API_KEY=SG....
Equality of Some - values
- Some
#[allow(clippy::unnecessary_literal_unwrap)] fn main() { let x = Some(3); let y = Some(3); println!("{:?}", x); // `Option<{integer}>` cannot be formatted with the default formatter println!("{}", x == y); let z = Some(&3); println!("{:?}", z); // `Option<&{integer}>` cannot be formatted with the default formatter println!("{}", &x.unwrap() == z.unwrap()); }
Some(3)
true
Some(3)
true
Fork
[package]
name = "show-forking"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fork = "0.1.22"
use fork::{fork, Fork}; fn main() { println!("Before {}", std::process::id()); match fork() { Ok(Fork::Parent(child)) => { println!( "In parent process {}, new child has pid: {}", std::process::id(), child ); } Ok(Fork::Child) => { println!("In child process {}", std::process::id()); std::process::exit(0); } Err(_) => println!("Fork failed"), } println!("Launched {}", std::process::id()); println!("After {}", std::process::id()); }
TODO: wait, waitpid?
sysinfo - Which Operating System are we running on?
-
systinfo
-
kernel_version
-
os_version
[package]
name = "system-info"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sysinfo = "0.29"
use sysinfo::{System, SystemExt}; fn main() { let sys = System::new_all(); println!("System name: {}", sys.name().unwrap()); println!("System kernel version: {}", sys.kernel_version().unwrap()); println!("System OS version: {}", sys.os_version().unwrap()); println!("System host name: {}", sys.host_name().unwrap()); println!(); println!("NB CPUs: {}", sys.cpus().len()); println!(); println!("total memory: {} bytes", sys.total_memory()); println!("used memory : {} bytes", sys.used_memory()); println!("total swap : {} bytes", sys.total_swap()); println!("used swap : {} bytes", sys.used_swap()); }
System name: Ubuntu
System kernel version: 6.5.0-10-generic
System OS version: 23.10
System host name: code-maven
NB CPUs: 16
total memory: 29166940160 bytes
used memory : 10617876480 bytes
total swap : 8589930496 bytes
used swap : 0 bytes
Operating system information with os_info
-
os_info
-
os_type
-
architecture
[package]
name = "os-information"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
os_info = "3.7.0"
fn main() { let info = os_info::get(); // Print full information: println!("OS information: {info}"); // Print information separately: println!("Type: {}", info.os_type()); println!("Version: {}", info.version()); println!("Bitness: {}", info.bitness()); println!("Architecture: {}", info.architecture().unwrap()); }
OS information: Ubuntu 23.10 (mantic) [64-bit]
Type: Ubuntu
Version: 23.10
Bitness: 64-bit
Architecture: x86_64
Parse string to Rust expression using syn
- syn
- parse_str
use syn::{Expr, Result}; fn main() { match run() { Ok(_val) => println!("ok"), Err(err) => println!("err: {}", err), } cases(); } fn run() -> Result<()> { let code = ".."; let expr = syn::parse_str::<Expr>(code)?; let _res = match expr { syn::Expr::Range(_expr) => true, // _expr is the same expression that cannot be printed. _ => false, }; //println!("{}", res); Ok(()) } fn cases() { assert!(matches!( syn::parse_str::<Expr>("..").unwrap(), syn::Expr::Range(_expr) )); //let res = syn::parse_str::<Expr>("2 == 3").unwrap(); //assert!(matches!(res, syn::Expr::Range(_expr))); //assert!(matches!(expr, syn::Expr::If(_expr))); println!("done"); }
ok
done
Parse HTML
-
Html
-
parse
-
parse_document
-
CSS
-
Selector
-
attr
-
inner_html
use scraper::{Html, Selector}; fn main() { let html = r#" <!DOCTYPE html> <meta charset="utf-8"> <title>Hello, world!</title> <h1 class="foo">Hello, <i>world!</i></h1> "#; let document = Html::parse_document(html); let selector = Selector::parse("h1").unwrap(); for element in document.select(&selector) { assert_eq!(element.value().name(), "h1"); assert_eq!(element.attr("class").unwrap(), "foo"); assert_eq!(element.inner_html(), "Hello, <i>world!</i>"); assert!(element.has_children()); let x = element.has_children(); //let x = element.inner_html(); println!("{:?}", x); // The text content of an elemnet after removing all the markup let text = element.text().collect::<Vec<_>>().join(""); assert_eq!(text, "Hello, world!"); } let selector = Selector::parse("i").unwrap(); for element in document.select(&selector) { assert_eq!(element.value().name(), "i"); assert_eq!(element.inner_html(), "world!"); assert!(element.has_children()); let x = element.has_children(); //let x = element.inner_html(); println!("{:?}", x); } }
Fix URL parameter
-
trim_end_matches
-
url
-
The user can provide a URL, but I would like to be flexible and accept both with and without a trailing slash:
-
https://rust.code-maven.com
-
https://rust.code-maven.com/
At first I tried some over-engineered solutions, till I got the recommendation to use trim_end_matches
.
fn main() { for url in [ String::from("https://rust.code-maven.com"), String::from("https://rust.code-maven.com/"), ] { process_rec(&url); process_mut(&url); pre_process(&url); trim_if_needed(&url); } } fn trim_if_needed(url: &str) { process(url.trim_end_matches('/')); } fn process_rec(url: &str) { if url.ends_with('/') { return process_rec(&url[0..url.len() - 1]); } process(url); } fn process_mut(mut url: &str) { if url.ends_with('/') { url = &url[0..url.len() - 1]; } process(url); } fn pre_process(url: &str) { if url.ends_with('/') { return process(&url[0..url.len() - 1]); } process(url) } fn process(url: &str) { fetch(url); fetch(&format!("{}/page", url)); } fn fetch(url: &str) { println!("Process '{}'", url); }
However using the url crate might be the best solution in this case:
[package]
name = "fix-url"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
url = "2.5.0"
fn main() { for site in [ String::from("https://rust.code-maven.com"), String::from("https://rust.code-maven.com/"), ] { process(url::Url::parse(&site).unwrap()); } } fn process(site: url::Url) { fetch(&site); fetch(&site.join("page").unwrap()); } fn fetch(site: &url::Url) { println!("Process '{}'", site); }
My little Rust runner
- This is especially useful for the slides so I can create individual Rust example files and run them stand alone.
#!/usr/bin/bash -e
if [ "$1" == "" ];
then
echo "Usage: $0 path/to/file.rs"
exit 1
fi
# rustc $1
# name=$(basename $1 .rs)
# ./$name
rustc $1 -o myexe
shift
if [ -f myexe ];
then
./myexe $*
\rm myexe
fi
./rust.sh examples/intro/hello.rs
Ordered floats
-
TODO
Linked list using struct and Box
- Box
#[derive(Debug)] #[allow(dead_code)] struct Thing { data: String, next: Option<Box<Thing>>, } fn main() { let a = Thing { data: String::from("First"), next: None, }; println!("{:#?}", a); let b = Thing { data: String::from("Second"), next: Some(Box::new(a)), }; println!("{:#?}", b); let c = Thing { data: String::from("Third"), next: Some(Box::new(b)), }; println!("{:#?}", c); }
Thing {
data: "First",
next: None,
}
Thing {
data: "Second",
next: Some(
Thing {
data: "First",
next: None,
},
),
}
Thing {
data: "Third",
next: Some(
Thing {
data: "Second",
next: Some(
Thing {
data: "First",
next: None,
},
),
},
),
}
Undirected graph
#![allow(clippy::vec_init_then_push)] #[allow(dead_code)] #[derive(Debug)] struct Node { // Vertex, Point name: String, } #[allow(dead_code)] #[derive(Debug)] struct Edge<'a> { // Arcs a: &'a Node, b: &'a Node, } fn main() { let kneiphof = Node { name: String::from("Kneiphof"), }; let lomse = Node { name: String::from("Lomse"), }; let left = Node { name: String::from("Left bank"), }; let _right = Node { name: String::from("Right bank"), }; let mut edges: Vec<Edge> = vec![]; edges.push(Edge { a: &kneiphof, b: &lomse, }); edges.push(Edge { a: &left, b: &lomse, }); edges.push(Edge { a: &left, b: &lomse, }); edges.push(Edge { a: &left, b: &kneiphof, }); println!("{:#?}", edges); }
Memory leak
In this example we try to implement a memory leak as we demonstrate in Python showing how Rust makes us avoid it. So far this code has compilation errors.
#![allow(unused_variables)] #[allow(dead_code)] struct Thing { data: String, other: Option<Box<Thing>>, } fn main() { alloc(); } fn alloc() { let mut a = Thing { data: String::from("abc"), other: None, }; let mut b = Thing { data: String::from("abc"), other: None, }; a.other = Some(Box::new(b)); b.other = Some(Box::new(a)); }
Debug assertions
fn main() { println!("Hello, world!"); if cfg!(debug_assertions) { println!("debug_assertions"); } }
cargo run
cargo run --release
Passing string to function
- TODO
// Experiment to use a function accepting strings without explaining the references // to be used at the beginning of the course fn main() { let args = std::env::args().collect::<Vec<String>>(); println!("{}", add(args[1].clone(), args[2].clone())); } fn add(x: String, y: String) -> u32 { let a: u32 = x.parse().expect("number"); let b: u32 = y.parse().expect("number"); a + b }
Num traits
map with threads with Mutex
- Mutex
- TODO
use std::sync::{Condvar, Mutex}; use std::thread; fn main() { let numbers: Vec<i32> = (1..=10).collect(); println!("{:?}", numbers); let doubles = map_thread(&numbers, double_function, 3); println!("{:?}", doubles); } fn double_function(num: &i32) -> i32 { //println!("{:?}", std::thread::current().id()); 2 * num } fn map_thread<Tin: Sync, Tout: Send>( params: &[Tin], func: fn(&Tin) -> Tout, max_threads: i32, ) -> Vec<Tout> { let thread_count = Mutex::new(0); let notify_finish = Condvar::new(); // Use scoped threads so that we can pass references around thread::scope(|scope| { let mut handles = vec![]; for xref in params { // Wait for earlier threads to finish, if necessary let mut guard = thread_count.lock().unwrap(); while *guard >= max_threads { guard = notify_finish.wait(guard).unwrap(); } // Increment running thread count *guard += 1; handles.push(scope.spawn(|| { // Run calculation let res = func(xref); // Report success, decrement running thread count *(thread_count.lock().unwrap()) -= 1; notify_finish.notify_one(); // Return calculation result res })); } // Read the return values of each thread, in order handles.into_iter().map(|h| h.join().unwrap()).collect() }) }
Accessing envrionment variables
- std::env
- std::env::var
We can access the environment variables from Rust using the std::env::var function.
It returns a Result
.
use std::env; fn main() { for name in ["PATH", "RUST"] { println!("Checking {name}"); match env::var(name) { Ok(val) => println!("{name}={val}"), Err(err) => println!("Environment variable {name} does not exist.\n{err}"), } } }
Run as
cargo run
RUST=42 cargo run
List environment variables
- std::env
- std::env::vars
use std::env; fn main() { for (name, _value) in env::vars() { println!("Variable: {name}"); // match env::var(name) { // Ok(val) => println!("{name}={val}"), // Err(err) => println!("Environment variable {name} does not exist.\n{err}"), // } } }
Closures
fn main() { do_something(); do_something_more(); change_something(); } fn do_something() { let age = 42; let display = || println!("The age is {age}"); display(); } fn do_something_more() { let age = 42; let display = || { println!("Before"); println!("More age {age}"); println!("After"); }; display(); } fn change_something() { let mut age = 42; let mut change = || { age = 43; }; //println!("Age before {age}"); change(); println!("Age after {age}"); }
Reference to a number
- TODO
fn main() { copy_and_change(); reference_is_borrow(); reference_in_scope_is_borrow(); } fn copy_and_change() { let mut a = 23; let b = 23; a += 1; println!("{a} {:p}", &a); println!("{b} {:p}", &b); } fn reference_is_borrow() { let mut a = 23; a += 1; let b = &a; //a += 1; // cannot assign to `a` because it is borrowed println!("{a} {:p}", &a); println!("{b} {:p}", &b); } fn reference_in_scope_is_borrow() { let mut a = 23; a += 1; let b = &a; println!("{b} {:p}", &b); // the lifetime of the reference ends here a += 1; // cannot assign to `a` because it is borrowed println!("{a} {:p}", &a); }
Out of memory
This program will create an ever growing string. When it reaches the total size of the (free) memory in the computer it crashes.
fn main() { let n = 100; let mut text = String::from("x"); for i in 0..n { text.push_str(&text.clone()); println!("len: {} {}", i, text.len()); } }
Two leveles of modules
#![allow(dead_code)] fn main() { println!("in main"); //house::live(); //crate::house::live(); // kitchen is private //house::kitchen::cook(); //crate::house::kitchen::cook(); //crate::house::bathroom::shower(); //house::bathroom::shower(); house::bathroom::shaving(); } mod house { pub fn live() { println!("live"); kitchen::cook(); bathroom::shower(); } mod kitchen { pub fn cook() { println!("cook"); peal_potatoes(); } fn peal_potatoes() { println!("peal_potatoes"); } pub fn doing_dishes() { println!("doing_dishes"); } } pub mod bathroom { pub fn shower() { println!("shower"); use_soap() } fn use_soap() { println!("use_soap"); } pub fn shaving() { println!("shaving"); crate::house::kitchen::cook(); super::kitchen::cook(); } } }
Try packages
fn main() { println!("main"); in_main_rs(); // colors::red(); // private function colors::red(); crate::colors::red(); colors::deep::in_deep(); // colors::blue(); // // colors::blue_helper(); // error[E0603]: function `blue_helper` is private // colors::dark::blue(); // use colors::dark; // dark::green(); } fn in_main_rs() { println!("in_main_rs"); } mod colors { // fn red() { // println!("red"); // } pub fn red() { println!("public red"); deep::in_deep(); } // pub fn blue() { // println!("blue"); // blue_helper(); // can be called from here // } pub mod deep { pub fn in_deep() { println!("in_deep"); } } // fn blue_helper() { // println!("blue_helper"); // crate::in_main_rs(); // super::in_main_rs(); // } // pub mod dark { // pub fn blue() { // println!("dark_blue"); // crate::in_main_rs(); // absolute path // super::super::in_main_rs(); // relative path, probably not very good idea // super::red(); // } // pub fn green() { // println!("dark_green"); // } // } }
release.toml
- More than 500 crates have a file called
release.toml
in their git repository and 2 have.release.toml
. - These are configuration files of the cargo-release crate to make releases easier.
thousands crate for struct with Display (commafy)
- thousands
- commafy
use thousands::Separable; struct Point { x: i32, y: i32, } impl std::fmt::Display for Point { fn fmt(&self, format: &mut std::fmt::Formatter) -> std::fmt::Result { write!(format, "x{} {}x", self.x, self.y) } } fn main() { assert_eq!(12345.separate_with_commas(), "12,345"); assert_eq!("12345".separate_with_commas(), "12,345"); let p = Point { x: 1234, y: 4567 }; println!("{p}"); println!("{}", p.separate_with_commas()); }
x1234 4567x
x1,234 4567x
Early return on None Option
#![allow(unused)] #![allow(dead_code)] fn main() { fn compute(input: bool) -> Option<String> { if input { Some(String::from("text")) } else { None } } fn manual(input: bool) -> String { let result = compute(input); if result.is_none() { return String::from("Missing"); } let data = result.unwrap(); // process data: format!("Process {data}") } fn with_match(input: bool) -> String { let result = compute(input); match result { None => String::from("Missing"), Some(data) => { // process data: format!("Process {data}") } } } fn with_match_and(input: bool) -> String { let result = compute(input); let data = match result { None => return String::from("Missing"), Some(data) => data, }; // process data: format!("Process {data}") } macro_rules! ok_or_return { ($cond: expr, $result: expr) => { match $cond { None => return $result, Some(data) => data, } }; } fn with_macro(input: bool) -> String { let result = compute(input); let data = ok_or_return!(result, String::from("Missing")); // process data: format!("Process {data}") } fn let_else(input: bool) -> String { let result = compute(input); let Some(data) = result else { return String::from("Missing"); }; // process data: format!("Process {data}") } #[test] fn test_compute() { assert_eq!(compute(true), Some(String::from("text"))); assert_eq!(compute(false), None); } #[test] fn test_manual() { assert_eq!(manual(true), String::from("Process text")); assert_eq!(manual(false), String::from("Missing")); } #[test] fn test_with_match() { assert_eq!(with_match(true), String::from("Process text")); assert_eq!(with_match(false), String::from("Missing")); } #[test] fn test_with_match_and() { assert_eq!(with_match_and(true), String::from("Process text")); assert_eq!(with_match_and(false), String::from("Missing")); } #[test] fn test_with_macro() { assert_eq!(with_match_and(true), String::from("Process text")); assert_eq!(with_match_and(false), String::from("Missing")); } #[test] fn test_let_else() { assert_eq!(let_else(true), String::from("Process text")); assert_eq!(let_else(false), String::from("Missing")); } }
Pattern matching with guards on a number
Rust does not take in account the guard conditions when checking if the arms cover all the possible values. In other words Rust cannot see that our first 3 conditions are exhaustive and that the last condition will never be reached. Thus Rust requires the catch-all _ to be present in order to compile the code
fn main() { let age = 65; match age { x if x < 18 => println!("You are a minor"), x if x >= 18 && x <= 65 => println!("You are an adult"), x if x>65 => println!("You are a senior citizen"), _ => println!("Invalid age") } }
egui
About eframe and egui
eGUI Window
- CentralPanel
- NativeOptions
- run_simple_native
cargo new egui-window
cd egui-window
cargo add eframe
This will make Cargo.toml look like this:
[package]
name = "egui-window"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
eframe = "0.24"
The code is here:
use eframe::egui; fn main() -> Result<(), eframe::Error> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; eframe::run_simple_native("Rust Maven egui App", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |_ui| {}); }) }
{% embed include file="src/examples/egui/egui-window/screenshot.png)
When you click on the X
to close the window, you might get lots of warnings like this:
warning: queue 0x7fa398000ca0 destroyed while proxies still attached:
This was reported here.
eGUI heading
- heading
use eframe::egui; fn main() -> Result<(), eframe::Error> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; eframe::run_simple_native("Rust Maven egui App", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Hello World"); }); }) }
eGUI label
- label
use eframe::egui; fn main() -> Result<(), eframe::Error> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; eframe::run_simple_native("Rust Maven egui App", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Hello World"); ui.label("A label"); }); }) }
eGUI counter with label and button
- button
- clicked
- label
use eframe::egui; fn main() -> Result<(), eframe::Error> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; let mut counter = 0; eframe::run_simple_native("Rust Maven egui App", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |ui| { if ui.button("Increment").clicked() { counter += 1; } ui.label(format!("Count {counter}")); }); }) }
- Every time we click on the button, the code in the block gets executed incrementing the counter
- Then the displayed label is refreshed.
Egui demo app
- Taken from the documentation
use eframe::egui; fn main() { let native_options = eframe::NativeOptions::default(); eframe::run_native( "My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))), ) .unwrap(); } #[derive(Default)] struct MyEguiApp {} impl MyEguiApp { fn new(_cc: &eframe::CreationContext<'_>) -> Self { // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. // Restore app state using cc.storage (requires the "persistence" feature). // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use // for e.g. egui::PaintCallback. Self::default() } } impl eframe::App for MyEguiApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Hello World!"); }); } }
images
Draw some simple images
- using the image crate
[package]
name = "crate-image"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = "0.24"
fn main() { let width = 800; let height = 800; let mut imgbuf = image::ImageBuffer::new(width, height); // gradient based on the example in the sourcde code // for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { // let red = (0.3 * x as f32) as u8; // let green = 0; // let blue = (0.3 * y as f32) as u8; // *pixel = image::Rgb([red, green, blue]); // } // horizontal red gradient // for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { // let red = (0.3 * x as f32) as u8; // let green = 0; // let blue = 0; // *pixel = image::Rgb([red, green, blue]); // } // vertical red gradient // for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { // let red = (0.3 * y as f32) as u8; // let green = 0; // let blue = 0; // *pixel = image::Rgb([red, green, blue]); // } // horizontal red line // for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { // let red = 255 as u8; // let green = 0; // let blue = 0; // if y == 23 { // *pixel = image::Rgb([red, green, blue]); // } // } // draw a horizontal line // let red = 100 as u8; // let green = 100; // let blue = 0; // let y = 200; // for x in 0..width { // // let pixel = imgbuf.get_pixel_mut(x, y); // // *pixel = image::Rgb([red, green, blue]); // *imgbuf.get_pixel_mut(x, y) = image::Rgb([red, green, blue]); // } // draw a vertical line let red = 100_u8; let green = 100; let blue = 0; let x = 200; for y in 0..height { // let pixel = imgbuf.get_pixel_mut(x, y); // *pixel = image::Rgb([red, green, blue]); *imgbuf.get_pixel_mut(x, y) = image::Rgb([red, green, blue]); } imgbuf.save("image.png").unwrap(); }
Binary
Read binary file into a vector of u8
fn main() { let args = std::env::args().collect::<Vec<_>>(); if args.len() != 2 { println!("Usage: {} FILENAME", args[0]); std::process::exit(1); } let filename = &args[1]; println!("filename {filename}"); let data: Vec<u8> = std::fs::read(filename).unwrap(); println!("len: {}", data.len()); }
unsafe
unsafe keywords
unsafe powers
-
TODO
-
Dereference a raw pointer
-
Call an unsafe function or method
-
Access or modify a mutable static variable
-
Implement an unsafe trait
-
Access fields of a union
fn main() { let mut text = String::from("abcd"); println!("{text}"); let pointer: *mut String = &mut text as *mut String; // mutable raw pointer println!("pointer is: {:?}", pointer); println!("addr_of: {:?}", std::ptr::addr_of!(text)); unsafe { // pointer.offset(count); println!("pointer+1 is: {:?}", pointer.offset(1)); println!("pointer is: {}", *pointer); //println!("ptr is: {}", *ptr.offset(1)); } println!("{text}"); // let address = 0x012345usize; // let r = address as *const i32; // println!("{r:?}"); //safe_increment(); // unsafe_increment(); } // fn safe_increment() { // let mut num = 5; // let r1 = # // num += 1; // println!("{num}"); // println!("{r1}"); // } // fn unsafe_increment() { // let mut num = 5; // let r1 = &num as *const i32; // immutable raw pointer // let r2 = &mut num as *mut i32; // mutable raw pointer // println!("r1 is: {:?}", r1); // println!("r2 is: {:?}", r2); // unsafe { // println!("r1 is: {}", *r1); // println!("r2 is: {}", *r2); // *r2 += 1; // } // println!("{num}"); // }
Unsafe - core dump
- TODO
fn main() { let numbers = vec![3, 5, 7]; let ptr_1 = numbers.as_ptr() as *mut i32; println!("{:?}", numbers); //println!("{ptr_1:?}"); println!("{ptr_1:p}"); let mut ptr_1 = ptr_1 as usize; ptr_1 += 4; let nums = unsafe { Vec::from_raw_parts(ptr_1 as *mut i32, numbers.len() - 1, numbers.capacity() - 1) }; println!("{:?}", nums); }
Sea ORM
Counter with Sea-ORM
- TODO
[package]
name = "counter"
version = "0.1.0"
edition = "2021"
[dependencies]
futures = "0.3.30"
sea-orm = { version = "1.0.1", features = ["sqlx-sqlite", "runtime-async-std-native-tls", "macros"] }
use sea_orm::{ConnectOptions, Database, DbErr}; // cargo install sea-orm-cli use futures::executor::block_on; fn main() { if let Err(err) = block_on(run()) { panic!("{}", err); } } async fn run() -> Result<(), DbErr> { let args = std::env::args().collect::<Vec<_>>(); if args.len() > 2 { println!("Usage: {} [NAME]", args[0]); std::process::exit(1); } let opt = ConnectOptions::new("sqlite:./counter.db?mode=rwc"); let _db = Database::connect(opt).await?; if args.len() == 1 { list_counters(); } else { let name = &args[1]; increment(name); } Ok(()) } fn list_counters() { println!("list"); } fn increment(name: &str) { println!("{name}:"); }