Initial commit
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/rust
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=rust
|
||||
|
||||
### Rust ###
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/rust
|
||||
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,31 @@
|
||||
// Declarative macros
|
||||
|
||||
// macro_rules! custom_vec {
|
||||
// ( $( $x:expr ),* ) => {
|
||||
// {
|
||||
// let mut temp_vec = Vec::new();
|
||||
// $(
|
||||
// temp_vec.push($x);
|
||||
// )*
|
||||
// temp_vec
|
||||
// }
|
||||
// };
|
||||
// // ( $( $x:expr ),*; mult = $mult:expr ) => {
|
||||
// // custom_vec![$( $x * $mult ),*]
|
||||
// // };
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
let mut a = Vec::new();
|
||||
a.push(1);
|
||||
a.push(2);
|
||||
a.push(3);
|
||||
|
||||
// let a = vec![1, 2, 3];
|
||||
|
||||
// let a = custom_vec!(1, 2, 3);
|
||||
|
||||
// let a = custom_vec!(1, 2, 3; mult = 2);
|
||||
|
||||
println!("{a:?}");
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace]
|
||||
members = ["demo_macro"]
|
||||
|
||||
[dependencies]
|
||||
demo_macro = { path = "demo_macro" }
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "demo_macro"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.106"
|
||||
quote = "1.0.45"
|
||||
syn = "2.0.117"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
@@ -0,0 +1,70 @@
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn hello_world(_item: TokenStream) -> TokenStream {
|
||||
"println!(\"Hello, World\")".parse().unwrap()
|
||||
}
|
||||
|
||||
// #[proc_macro]
|
||||
// pub fn make_answer(_item: TokenStream) -> TokenStream {
|
||||
// "fn answer() -> u32 { 42 }".parse().unwrap()
|
||||
// }
|
||||
|
||||
// use quote::quote;
|
||||
//
|
||||
// #[proc_macro]
|
||||
// pub fn make_answer_quote(_item: TokenStream) -> TokenStream {
|
||||
// let value: u32 = 43;
|
||||
//
|
||||
// quote! {
|
||||
// fn answer_quote() -> u32 {
|
||||
// #value
|
||||
// }
|
||||
// }
|
||||
// .into()
|
||||
// }
|
||||
|
||||
// use quote::ToTokens;
|
||||
// use syn::{
|
||||
// Error, LitInt, Token,
|
||||
// parse::{Parse, ParseStream},
|
||||
// parse_macro_input,
|
||||
// };
|
||||
//
|
||||
// struct Custom {
|
||||
// value: LitInt,
|
||||
// }
|
||||
//
|
||||
// impl Parse for Custom {
|
||||
// // answer = <value>
|
||||
// fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
// let ident: syn::Ident = input.parse()?;
|
||||
// if ident != "answer" {
|
||||
// return Err(Error::new(ident.span(), "expected 'answer'"));
|
||||
// }
|
||||
//
|
||||
// input.parse::<Token![=]>()?;
|
||||
//
|
||||
// let value = input.parse()?;
|
||||
//
|
||||
// Ok(Custom { value })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl ToTokens for Custom {
|
||||
// fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
// self.value.to_tokens(tokens);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[proc_macro]
|
||||
// pub fn make_answer_custom(input: TokenStream) -> TokenStream {
|
||||
// let custom = parse_macro_input!(input as Custom);
|
||||
//
|
||||
// quote! {
|
||||
// fn answer_custom() -> u32 {
|
||||
// #custom
|
||||
// }
|
||||
// }
|
||||
// .into()
|
||||
// }
|
||||
@@ -0,0 +1,23 @@
|
||||
use demo_macro::hello_world;
|
||||
|
||||
// use demo_macro::make_answer;
|
||||
// make_answer!();
|
||||
|
||||
// use demo_macro::make_answer_quote;
|
||||
// make_answer_quote!();
|
||||
|
||||
// use demo_macro::make_answer_custom;
|
||||
// make_answer_custom!(answer = 33);
|
||||
|
||||
fn main() {
|
||||
hello_world!();
|
||||
|
||||
// let a = answer();
|
||||
// println!("{a}");
|
||||
|
||||
// let b = answer_quote();
|
||||
// println!("{b}");
|
||||
|
||||
// let c = answer_custom();
|
||||
// println!("{c}");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,26 @@
|
||||
// Procedural macro
|
||||
// Derive macro
|
||||
|
||||
#[derive(Debug)]
|
||||
struct User {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for User {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {}", self.first_name, self.last_name)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let tim = User {
|
||||
first_name: "Tim".into(),
|
||||
last_name: "Huizinga".into(),
|
||||
email: "tim@huizinga.dev".into(),
|
||||
};
|
||||
|
||||
println!("User: {tim}");
|
||||
println!("User: {tim:?}");
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde-xml-rs = "0.8.2"
|
||||
serde_json = "1.0.149"
|
||||
serde_yaml = "0.9.34"
|
||||
@@ -0,0 +1,45 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
// #[serde(rename_all = "camelCase")]
|
||||
// #[serde(rename_all = "PascalCase")]
|
||||
enum Access {
|
||||
Guest { vip: bool },
|
||||
Normal,
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
// #[serde(rename_all = "camelCase")]
|
||||
// #[serde(rename_all = "PascalCase")]
|
||||
struct User {
|
||||
// #[serde(rename = "@firstName")]
|
||||
first_name: String,
|
||||
// #[serde(rename = "@lastName")]
|
||||
last_name: String,
|
||||
// #[serde(skip)]
|
||||
// #[serde(rename = "@email")]
|
||||
email: String,
|
||||
access: Access,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let content = std::fs::read_to_string("user.json").unwrap();
|
||||
let user: User = serde_json::from_str(&content).unwrap();
|
||||
|
||||
println!("{user:#?}");
|
||||
|
||||
// let tim = User {
|
||||
// first_name: "Tim".into(),
|
||||
// last_name: "Huizinga".into(),
|
||||
// email: "tim@huizinga.dev".into(),
|
||||
// access: Access::Normal,
|
||||
// // access: Access::Guest { vip: true },
|
||||
// };
|
||||
//
|
||||
// println!("{}", serde_json::to_string_pretty(&tim).unwrap());
|
||||
//
|
||||
// println!("{}", serde_yaml::to_string(&tim).unwrap());
|
||||
//
|
||||
// println!("{}", serde_xml_rs::to_string(&tim).unwrap());
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"first_name": "Sebastiano",
|
||||
"last_name": "Tronto",
|
||||
"email": "sebastiano@tronto.net",
|
||||
"access": "Normal"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export DATABASE_URL=postgres://postgres:rustiscool@localhost/postgres
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sqlx = { version = "0.8.6", features = ["macros", "postgres", "runtime-tokio"] }
|
||||
tokio = { version = "1.51.1", features = ["full"] }
|
||||
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
database:
|
||||
image: postgres:18
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
- TZ=Europe/Amsterdam
|
||||
- PGTZ=Europe/Amsterdam
|
||||
- POSTGRES_PASSWORD=rustiscool
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
|
||||
interval: 10s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
timeout: 10s
|
||||
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO users (first_name, last_name) VALUES ('Tim', 'Huizinga');
|
||||
INSERT INTO users (first_name, last_name) VALUES ('Sebastiano', 'Tronto');
|
||||
@@ -0,0 +1 @@
|
||||
SELECT (first_name) FROM users;
|
||||
@@ -0,0 +1,23 @@
|
||||
use sqlx::{Connection, PgConnection};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let url = std::env::var("DATABASE_URL").unwrap();
|
||||
let mut conn = PgConnection::connect(&url).await.unwrap();
|
||||
|
||||
// sqlx::migrate!("migrations").run(&conn).await?;
|
||||
|
||||
let users = sqlx::query!("SELECT * FROM users")
|
||||
.fetch_all(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// let users = sqlx::query_file!("src/get_users.sql")
|
||||
// .fetch_all(&mut conn)
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
println!("{users:#?}");
|
||||
|
||||
// users[0].first_name;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "workshop"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace]
|
||||
members = ["workshop_macro"]
|
||||
|
||||
[dependencies]
|
||||
workshop_macro = { path = "./workshop_macro" }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
@@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
// Trigger a rebuild if the schema file has changed
|
||||
println!("cargo::rerun-if-changed=schema.json");
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use workshop_macro::from_schema;
|
||||
|
||||
from_schema!("https://download.huizinga.dev/workshop/schema.json");
|
||||
|
||||
fn main() {
|
||||
let content = std::fs::read_to_string("./users.json").unwrap();
|
||||
|
||||
let users: Vec<User> = serde_json::from_str(&content).unwrap();
|
||||
|
||||
println!("{users:#?}");
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"first_name": "Tim",
|
||||
"last_name": "Huizinga",
|
||||
"age": 28,
|
||||
"admin": true
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "workshop_macro"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.106"
|
||||
quote = "1.0.45"
|
||||
reqwest = { version = "0.13.2", features = ["blocking"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
syn = "2.0.117"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
@@ -0,0 +1,34 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{LitStr, parse_macro_input};
|
||||
|
||||
// HINT: Maybe with creating some structs/enums that match with the format of schema.json?
|
||||
|
||||
// HINT: The word `type` is reserved in rust but can still be used if you write `r#type`
|
||||
|
||||
// HINT: Maybe implementing ToTokens can be useful?
|
||||
|
||||
// HINT: You can access fields in the quote macro (e.g. `#schema.name`) so instead first bind it
|
||||
// to a local variable (e.g. `let name = &schema.name`).
|
||||
|
||||
// HINT: If you place use a string variable inside of the quote macro it will place it as a
|
||||
// string (e.g. "User"). You need to first create a syn::Ident
|
||||
// (see: https://docs.rs/syn/latest/syn/struct.Ident.html)
|
||||
|
||||
// HINT: If you have a iterable of qoutes! you can use and expand this in another quote macro
|
||||
// with `#(#fields),*`, similar to how it works with declarative macros.
|
||||
|
||||
// HINT: Since the macro will be placed in the context of the "callsite" it is best to use the
|
||||
// fully qualified path (e.g. `serde::Deserialize`), otherwise the user of the macro needs to
|
||||
// manually add `use serde::Deserialize` to their file.
|
||||
|
||||
#[proc_macro]
|
||||
pub fn from_schema(input: TokenStream) -> TokenStream {
|
||||
let url = parse_macro_input!(input as LitStr).value();
|
||||
let schema = reqwest::blocking::get(url).unwrap().text().unwrap();
|
||||
|
||||
// TODO: Print out the received schema text during compilation
|
||||
println!("{schema:#?}");
|
||||
|
||||
quote! {}.into()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "workshop"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace]
|
||||
members = ["workshop_macro"]
|
||||
|
||||
[dependencies]
|
||||
workshop_macro = { path = "./workshop_macro" }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
@@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
// Trigger a rebuild if the schema file has changed
|
||||
println!("cargo::rerun-if-changed=schema.json");
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "User",
|
||||
"fields": [
|
||||
{
|
||||
"name": "first_name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "last_name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "age",
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"name": "admin",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use workshop_macro::from_schema;
|
||||
|
||||
from_schema!("https://download.huizinga.dev/workshop/schema.json");
|
||||
|
||||
fn main() {
|
||||
let content = std::fs::read_to_string("./users.json").unwrap();
|
||||
|
||||
let users: Vec<User> = serde_json::from_str(&content).unwrap();
|
||||
|
||||
println!("{users:#?}");
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"first_name": "Tim",
|
||||
"last_name": "Huizinga",
|
||||
"age": 28,
|
||||
"admin": true
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "workshop_macro"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.106"
|
||||
quote = "1.0.45"
|
||||
reqwest = { version = "0.13.2", features = ["blocking"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
syn = "2.0.117"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
@@ -0,0 +1,96 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::{ToTokens, quote};
|
||||
use serde::Deserialize;
|
||||
use syn::{Ident, LitStr, parse_macro_input};
|
||||
|
||||
// HINT: Maybe with creating some structs/enums that match with the format of schema.json?
|
||||
|
||||
// HINT: The word `type` is reserved in rust but can still be used if you write `r#type`
|
||||
|
||||
// HINT: Maybe implementing ToTokens can be useful?
|
||||
|
||||
// HINT: You can access fields in the quote macro (e.g. `#schema.name`) so instead first bind it
|
||||
// to a local variable (e.g. `let name = &schema.name`).
|
||||
|
||||
// HINT: If you place use a string variable inside of the quote macro it will place it as a
|
||||
// string (e.g. "User"). You need to first create a syn::Ident
|
||||
// (see: https://docs.rs/syn/latest/syn/struct.Ident.html)
|
||||
|
||||
// HINT: If you have a iterable of qoutes! you can use and expand this in another quote macro
|
||||
// with `#(#fields),*`, similar to how it works with declarative macros.
|
||||
|
||||
// HINT: Since the macro will be placed in the context of the "callsite" it is best to use the
|
||||
// fully qualified path (e.g. `serde::Deserialize`), otherwise the user of the macro needs to
|
||||
// manually add `use serde::Deserialize` to their file.
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum Type {
|
||||
String,
|
||||
Number,
|
||||
Bool,
|
||||
}
|
||||
|
||||
impl ToTokens for Type {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let ts = match self {
|
||||
Type::String => quote! { String },
|
||||
Type::Number => quote! { i64 },
|
||||
Type::Bool => quote! { bool },
|
||||
};
|
||||
|
||||
tokens.extend(ts);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Field {
|
||||
name: String,
|
||||
r#type: Type,
|
||||
}
|
||||
impl ToTokens for Field {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let name = Ident::new(&self.name, Span::call_site());
|
||||
let r#type = &self.r#type;
|
||||
|
||||
tokens.extend(quote! {
|
||||
#name: #r#type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Schema {
|
||||
name: String,
|
||||
fields: Vec<Field>,
|
||||
}
|
||||
|
||||
impl ToTokens for Schema {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let name = Ident::new(&self.name, Span::call_site());
|
||||
|
||||
let fields = &self.fields;
|
||||
|
||||
tokens.extend(quote! {
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct #name {
|
||||
#(#fields),*
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn from_schema(input: TokenStream) -> TokenStream {
|
||||
let url = parse_macro_input!(input as LitStr).value();
|
||||
let schema = reqwest::blocking::get(url).unwrap().text().unwrap();
|
||||
|
||||
let schema: Schema = serde_json::from_str(&schema).unwrap();
|
||||
|
||||
// HINT: There are two different TokenStreams, proc_macro::TokenStream (which the macro should
|
||||
// return) and proc_macro2::TokenStream (which syn uses). This can be a bit confusing, so you
|
||||
// will likely need to call `.into()` on your final TokenStream to convert it to the correct
|
||||
// type.
|
||||
schema.to_token_stream().into()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Knowledge Group Advanced Software: Rust macros
|
||||
|
||||
This repo contains the examples and short workshop (and possible solution) from the talk.
|
||||
|
||||
## Notes:
|
||||
|
||||
Example 05 requires that you are running a postgres database, this can be started by running `docker compose up` in the example directory.
|
||||
It also requires that the `DATABASE_URL` environment variable is set, this can be achieved by sourcing the `.env` file.
|
||||
|
||||
To setup the schema and data in the database you can use `sqlx-cli` (install: `cargo install sqlx-cli`), to apply to migrations run:
|
||||
|
||||
```bash
|
||||
sqlx migrate run
|
||||
```
|
||||
|
||||
For the workshop in 06 several hints are giving on things that might cause trouble.
|
||||
To help debug the macro you can install `cargo expand`: `cargo install cargo-expand`.
|
||||
Reference in New Issue
Block a user