Compare commits

...

10 commits

Author SHA1 Message Date
Sander Hautvast
cdff0e5fb0 part 4 2022-06-11 14:09:34 +02:00
Sander Hautvast
0c94f8331f added text 2022-06-08 22:41:22 +02:00
Sander Hautvast
baa20031e0 typos 2022-05-30 20:05:15 +02:00
Sander Hautvast
e07bed4a28 added author tags 2022-05-30 18:35:58 +02:00
Sander Hautvast
766b5ecb19 enterprisey part2 2022-05-30 18:32:46 +02:00
Sander Hautvast
c2b2bf281b added github link 2022-05-27 22:36:26 +02:00
Sander Hautvast
5e63427adf added links to rust-lang 2022-02-07 16:31:48 +01:00
Sander Hautvast
ee9f28f2e2 really minor 2022-02-07 16:18:42 +01:00
Sander Hautvast
d4d8c835b4 updated and published post on rust vs java 2022-02-07 16:15:00 +01:00
Sander Hautvast
4fdd4bd079 added reference 2022-02-06 15:50:54 +01:00
14 changed files with 726 additions and 41 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
public/ public/
.hugo_build.lock

View file

@ -14,3 +14,17 @@ theme = "beautifulhugo"
[Permalinks] [Permalinks]
post = "/:filename/" post = "/:filename/"
[markup]
[markup.highlight]
anchorLineNos = false
codeFences = true
guessSyntax = false
hl_Lines = ''
lineAnchors = ''
lineNoStart = 1
lineNos = false
lineNumbersInTable = true
noClasses = true
style = 'monokai'
tabWidth = 4

View file

@ -2,6 +2,7 @@
title: "Distrust Antipattern" title: "Distrust Antipattern"
date: 2022-01-28T10:47:56+01:00 date: 2022-01-28T10:47:56+01:00
draft: true draft: true
author: Sander Hautvast
--- ---
__can you trust your own team mates?__ __can you trust your own team mates?__

274
content/post/enterprisey.md Normal file
View file

@ -0,0 +1,274 @@
---
title: "Get Enterprisey with Rust"
date: 2022-05-23T17:33:09+02:00
author: Sander Hautvast
draft: false
---
Why not use the coolest language out there to do the things you probably still use Java for? Rust is marketed as a 'systems language', whatever that is. It looks to me like a general purpose language, 'turing complete' and whatnot. There are plenty crates for anything web related. There's tools for http servers, database connections, logging. We may also want security, monitoring, telemetry, cloud deployment. Do we want end-to-end testing? Sure. Openapi? Love it.
That said, a framework like spring-boot is pretty mature. It may just be a hassle trying to accomplish those nice features...
_Challenge accepted..._
Start a new project
{{<highlight bash>}}
cargo new hello-web
{{</highlight>}}
## Web framework.
There are three crates on the shortlist
- [Actix-web](https://crates.io/crates/actix-web)
- [Warp](https://crates.io/crates/warp)
- [Axum](https://crates.io/crates/axum)
This is not going to be an exhaustive comparison. I chose Axum based on [this](https://kerkour.com/rust-web-framework-2022). And I like the fact that its from the developers of [Tokio](https://tokio.rs/), let's say the [netty](https://netty.io/) of the rust world.
A hello world from Axum (found [here](https://github.com/tokio-rs/axum/tree/main/examples/hello-world))
{{<highlight rust "linenos=table">}}
use axum::{response::Html, routing::get, Router};
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(handler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handler() -> Html<&'static str> {
Html("<h1>Hello, World!</h1>")
}
{{</highlight>}}
That's pretty straightforward. It'll get more complicated than this, but this code is on par with any webframework out there.
```Cargo.toml
axum = "0.5.6"
```
## Restartability
Haven't you added this to your -ilities? Still restarting manually? You shouldn't have to!
It became a must-have for web development, and of course it is available for java as well, but al too often it is lacking from, what have you, the average maven project (or does it simply take too long?)
Luckily for us, using a cargo plugin this is a breeze:
{{<highlight bash "linenos=table">}}
cargo install cargo-watch
cargo watch -x run
{{</highlight>}}
## Logging
There are 3 options (probably more):
* [env_logger](https://crates.io/crates/env_logger): simple to set up
* [log4rs](https://crates.io/crates/log4rs), more advanced use cases, modelled after the now infamous log4j
* [tracing](https://crates.io/crates/tracing), 'tracing is a framework for instrumenting Rust programs to collect structured, event-based diagnostic information.'
Useful comparison [here](https://medium.com/nerd-for-tech/logging-in-rust-e529c241f92e)
Here I would have opted for env_logger, because we have a simple project, but I ended up with tracing, because it integrates really well with tokio and axum. It's also very well suited for larger environments and other subscribers to logging events than mere log files. Read [here](https://burgers.io/custom-logging-in-rust-using-tracing) if you want to know more.
```Cargo.toml
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
```
Add a simple tracing 'subscriber' to your code. It will receive log events and write them to stdout.
{{<highlight rust "linenos=table">}}
use tracing::{debug,Level};
use tracing_subscriber::FmtSubscriber;
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::TRACE)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("setting default subscriber failed");
{{</highlight>}}
## Database
We'll just use postgresql. Why not with podman?
```bash
podman machine init --cpus=1 --disk-size=10 --memory=2048 -v $HOME:$HOME
podman machine start
podman run -dt --name my-postgres -e POSTGRES_PASSWORD=1234 -v "postgres:/var/lib/postgresql/data:Z" -p 5432:5432 postgres
```
Article [here](https://medium.com/@butkovic/favoring-podman-over-docker-desktop-33368e031ba0)
### Connect to it
Of course we need to connect to the database from our program. For this we'll add
```Cargo.toml
sqlx = { version = "0.5.13", features = ["postgres", "runtime-tokio-native-tls"] }
```
and
{{<highlight rust "linenos=table">}}
use sqlx::postgres::{PgPool, PgPoolOptions};
let pool = PgPoolOptions::new()
.max_connections(5)
.connect_timeout(Duration::from_secs(3))
.connect(&db_connection_str)
.await
.expect("can't connect to database");
{{</highlight>}}
For this I looked at the [sqlx example](https://github.com/tokio-rs/axum/tree/main/examples/sqlx-postgres) for Axum. [Sqlx](https://crates.io/crates/sqlx) is a database driver for several databases, one of which is postgres. They state: _SQLx is not an ORM!_. There are ORM's built on top of Sqlx, but we're not going to use them.
### Setting up the schema
We're going to emulate spring-boot here: on server startup recreate the database using a script with ddl and dml statements. Not for production, but fine for local testing.
{{<highlight rust "linenos=table">}}
let create_database_sql = include_str!("create_database.sql");
let statements = create_database_sql.split(";");
for statement in statements {
sqlx::query(statement).execute(&pool).await.expect("error running script");
}
{{</highlight>}}
Add _create_database.sql_ to _src_:
{{<highlight sql "linenos=table">}}
drop table if exists blog_entry;
create table blog_entry
(
title varchar(100),
author varchar(40),
text text
);
insert into blog_entry(created, title, author, text)
values ('Get enterprisey with Rust', 'Sander', 'Lorem Ipsum');
insert into blog_entry(created, title, author, text)
values ('Get whimsical with data', 'Sander', 'Lorem Ipsum');
{{</highlight>}}
Note that ```include_str``` is a macro that reads a file that is part of the compilation unit, similar to reading a resource from the java classpath. Like in JDBC you have to split the file into individual statements and then execute them.
### Create a service that returns Json
[Serde](https://crates.io/crates/serde) is the default for working with Json, so add ```serde = "1.0.137"``` to your Cargo.toml.
Next we create an object that can be serialized:
{{<highlight rust>}}
#[derive(Serialize, Deserialize, Clone, Debug, sqlx::FromRow)]
struct BlogEntry{
title: String,
author: String,
text: String
}
{{</highlight>}}
As you can see a whole lot of derived traits are being used.
* ```Serialized``` and ```Deserialized``` for translating from and to Json.
* ```Clone``` and ```Debug``` are not stricly necessary, but come in handy.
* ```sqlx::FromRow``` automagically maps the database result to the desired type.
Now we have to create a function that executes the query and hands back the result to the client.
{{<highlight rust "linenos=table">}}
use axum::{extract::Extension, http::StatusCode, Json, Router, routing::get};
async fn get_blogs(Extension(pool): Extension<PgPool>) -> Result<Json<Vec<BlogEntry>>, (StatusCode, String)> {
debug!("handling request");
sqlx::query_as("select title, author, text from blog_entry")
.fetch_all(&pool)
.await
.map(|r| Json(r))
.map_err(internal_error)
}
{{</highlight>}}
* async function
* Note the peculiar syntax ```Extension(pool): Extension<PgPool>```. This is pattern matching on function arguments. The actual argument will be passed by Axum. We only need the pool and this way we can extract it from the Extension.
* For Json you need to wrap the result ```Vec<BlogEntry>``` in a ```axum::Json``` struct.
* ```map_err``` is called with function argument ```internal_error```. This function maps any runtime error to http code 500.
{{<highlight rust "linenos=table">}}
fn internal_error<E>(err: E) -> (StatusCode, String)
where
E: std::error::Error,
{
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
}
{{</highlight>}}
Now we need to set up the server so that a ```/entries``` route is mapped to our newly created function. In ```main``` remove the _hello world_ handler and add this:
{{<highlight rust "linenos=table">}}
let app = Router::new()
.route("/entries", get(get_blogs))
.layer(Extension(pool));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
debug!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
{{</highlight>}}
### Working with dates
Dates are indispensible in pretty much any enterprisey application. For this we will add the ```chrono``` crate. The rust standard library is not as comprehensive as in some other languages. This means that there is no formal standard for working with databases, or dates, but in many cases (serde, chrono, tokio) there are _de facto_ standards. This means that there are also implementations in serde and sqlx for working with chrono. For them to work we need to add _features_ for both in the Cargo.toml:
```Cargo.toml
sqlx = { version = "0.5.13", features = ["postgres", "runtime-tokio-native-tls", "chrono"] }
chrono = {version = "0.4", features = ["serde"]}
```
Or rather add _chrono_ feature to sqlx and add _serde_ to _chrono_. I don't really know why this is not symmetrical. At least it works.
We add a _created_ date type to the BlogEntry struct:
{{<highlight rust>}}
use chrono::{DateTime, Utc};
#[derive(Serialize, Deserialize, Clone, Debug, sqlx::FromRow)]
struct BlogEntry {
created: DateTime<Utc>,
//...
}
{{</highlight>}}
and add a column to _create_database.sql_
{{<highlight sql>}}
create table blog_entry
(
created timestamptz,
//...
);
insert into blog_entry(created, title, author, text)
values (now(), 'Get enterprisey with Rust', 'Sander', 'Lorem Ipsum');
insert into blog_entry(created, title, author, text)
values (now(), 'Get whimsical with data', 'Sander', 'Lorem Ipsum');
{{</highlight>}}
We now have a http rest service on port 3000 that serves blog entries from a postgres database.
```bash
$ curl http://localhost:3000/entries
[{"created":"2022-05-27T06:45:27.750171Z","title":"Get enterprisey with Rust","author":"Sander","text":"Lorem Ipsum"},{"created":"2022-05-27T06:45:27.756820Z","title":"Get whimsical with data","author":"Sander","text":"Lorem Ipsum"}]
%
```
Neat!
Now, this is not production ready. We need automated tests, security, what about monitoring or performance metrics?
OpenAPI implementation is also on my whishlist.
Also what about database migrations? At this point I honestly don't know if there is like _flyway_ for rust. I'll have to get back to you on this.
The full code so far is on https://github.com/shautvast/rust-rest-service.
...to be continued

View file

@ -0,0 +1,143 @@
---
title: "Get Enterprisey with Rust part 2 - Input Validation"
date: 2022-05-30T17:50:11+02:00
author: Sander Hautvast
draft: false
---
Input validation is next on the list of enterprisey features (see also [part 1](/enterprisey)). This will be the only new feature to add in this post because we need to add quite a but of boilerplate (sadly) to get it working.
First we need to add a service that will respond to a post request on the same url.
Our app definition now looks like this:
{{<highlight rust "linenos=table">}}
let app = Router::new()
.route("/entries", get(get_blogs).post(add_blog))
.layer(Extension(pool));
{{</highlight>}}
And this is the ```add_blog``` function:
{{<highlight rust "linenos=table">}}
async fn add_blog(Extension(pool): Extension<PgPool>, ValidatedJson(blog): ValidatedJson<BlogEntry>) -> Result<Json<String>, (StatusCode, String)> {
debug!("handling BlogEntries request");
sqlx::query("insert into blog_entry (created, title, author, text) values ($1, $2, $3, $4)")
.bind(blog.created)
.bind(blog.title)
.bind(blog.author)
.bind(blog.text)
.execute(&pool)
.await
.map_err(internal_error)?;
Ok(Json("created".to_owned()))
}
{{</highlight>}}
This won't compile for now, because we still have to define ValidatedJson.
Here we see a different form of working with SQLx. We now have bind parameters. O right, that's a must have for performance and security! Note that there is also a macro called query!. The main advantage of this is that it does compile-time query verification! I did not include it here for simplicity. It needs a environment variable in the shell where you run Cargo.
Now, you could also have used axum::Json instead of the custom ValidatedJson type. It would try to deserialize the json String and raise an error if for instance the creation date is not in the right (ISO-8601) format.
For more (custom) validation we need the [validator](https://crates.io/crates/validator) crate.
```Cargo.toml
validator = { version = "0.15", features = ["derive"] }
```
Next we need to annotate the ```BlogEntry``` struct with our validation rules:
{{<highlight rust "linenos=table">}}
#[derive(Serialize, Deserialize, Clone, Debug, sqlx::FromRow, Validate)]
struct BlogEntry {
created: DateTime<Utc>,
#[validate(length(min = 10, max = 100, message = "Title length must be between 10 and 100"))]
title: String,
#[validate(email(message = "author must be a valid email address"))]
author: String,
#[validate(length(min = 10, message = "text length must be at least 10"))]
text: String,
}
{{</highlight>}}
* derive Validate trait
* annotations on the properties
So far so good...
{{<highlight rust "linenos=table">}}
use axum::{http::StatusCode, Json, response::{IntoResponse, Response}, Router, routing::get, BoxError};
use axum::extract::{Extension, FromRequest, RequestParts, Json as ExtractJson};
use thiserror::Error;
use validator::Validate;
use async_trait::async_trait;
#[derive(Debug, Clone, Copy, Default)]
pub struct ValidatedJson<T>(pub T);
#[async_trait]
impl<T, B> FromRequest<B> for ValidatedJson<T>
where
T: DeserializeOwned + Validate,
B: http_body::Body + Send,
B::Data: Send,
B::Error: Into<BoxError>,
{
type Rejection = ServerError;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let ExtractJson(value) = ExtractJson::<T>::from_request(req).await?;
value.validate()?;
Ok(ValidatedJson(value))
}
}
#[derive(Debug, Error)]
pub enum ServerError {
#[error(transparent)]
ValidationError(#[from] validator::ValidationErrors),
#[error(transparent)]
AxumFormRejection(#[from] axum::extract::rejection::JsonRejection),
}
impl IntoResponse for ServerError {
fn into_response(self) -> Response {
match self {
ServerError::ValidationError(_) => {
let message = format!("Input validation error: [{:?}]", self).replace('\n', ", ");
(StatusCode::BAD_REQUEST, message)
}
ServerError::AxumFormRejection(_) => (StatusCode::BAD_REQUEST, self.to_string()),
}
.into_response()
}
}
{{</highlight>}}
Quite a bit of cruft! And we need more crates:
```Cargo.toml
thiserror = "1.0.29"
http-body = "0.4.3"
async-trait = "0.1"
```
The good news is that this code is generic for your whole application. So I guess you can put it in a separate file and largely forget about it.
So now If we now execute:
{{<highlight bash "linenos=table">}}
curl http://localhost:3000/entries -X POST -d '{"created":"2022-05-30T17:09:00.000000Z", "title":"aha", "author":"a", "text": "2"}' -v -H "Content-Type:application/json"
{{</highlight>}}
The server responds with a severe 400:BAD_REQUEST:
{{<highlight bash "linenos=table">}}
Input validation error: [ValidationError(ValidationErrors({
"author": Field([ValidationError { code: "email", message: None, params: {"value": String("a")} }]),
"text": Field([ValidationError { code: "length", message: Some("text length must be at least 10"), params: {"value": String("2"), "min": Number(10)} }]),
"title": Field([ValidationError { code: "length", message: Some("Title length must be between 10 and 100"), params: {"max": Number(100), "value": String("aha"), "min": Number(10)} }])}))]%
{{</highlight>}}
And voila!
### Final remark
Check out the [crate documentation](https://docs.rs/validator/0.15.0/validator/) for all available checks. Like in javax.validation you can also define completely custom ones.

View file

@ -0,0 +1,43 @@
---
title: "Get Enterprisey with Rust part 3 - Environments"
date: 2022-06-01T17:28:32+02:00
draft: false
---
This is the third installment in our series about dealing with enterprisey needs using rust.
* [part 1](/enterprisey) was about the initial setup with axum and postgres, logging and dates.
* [part 2](/enterprisey2) was devoted to input validation on incoming Json objects.
### Reading environment variables
I realized that the code I put in for part 1 on github already uses the built-in ```std::env::var``` function, but I didn't mention it in the blog.
{{<highlight rust "linenos=table">}}
let db_connection_str = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://postgres:1234@localhost".to_string());
{{</highlight>}}
The ```std::env::var``` function returns a ```Result``` that either contains the environment variable, or in the case that it is not found, the default, supplied in ```unwrap_or_else```.
Check out more [documentation](https://doc.rust-lang.org/book/ch12-05-working-with-environment-variables.html).
### Dotenv
[Dotenv](https://crates.io/crates/dotenv) is another option. Upon executing ```dotenv().ok()``` it reads a file with called _.env_ (in the current directory, or any parent) and puts the contents in your environment as variables. This might be more convient depending on your circumstances.
```Cargo.toml
dotenv = "0.15.0"
```
The code is now:
{{<highlight rust "linenos=table">}}
dotenv::dotenv().ok();
let db_connection_str = std::env::var("DATABASE_URL").unwrap();
{{</highlight>}}
Note that the ```unwrap``` function will abort the program, in case the DATABASE_URL is not found, but I also added _.env_ to the project root, so the program will run just fine (using cargo in the project directory). Of course, when deployed you need to make sure the file can be found and you must supply a environment specific version of it (to the container).
```
DATABASE_URL=postgres://postgres:1234@localhost
```
Dotenv also allows variable substitution within _.env_ files, allowing for less duplication.

View file

@ -0,0 +1,154 @@
---
title: "Get Enterprisey with Rust part 4 - Constants and regular expressions"
date: 2022-06-11T11:27:41+02:00
draft: false
---
This is the fourth part of the series on everyday programming tasks in the average CRUD application.
So far we covered:
1. [initial setup with axum and postgres, logging and dates](/enterprisey)
2. [input validation on incoming Json objects](/enterprisey2)
3. [working with environment variables](/enterprisey3)
This time we're going to look at another humble task: regular expressions and constants (often seen together).
`!warning!`
As it turns out this post dives deeper into some rust intricacies than the previous. So be cautious! You might learn something...
The beginning is not going to be complicated. We need two new crates:
```Cargo.toml
regex = "1.5"
lazy_static = "1.4"
```
First you will see how regular expressions work and next, how to make sure they are only compiled once (using constants), which is what you want, for performance.
### Raw strings
Imagine you need to remove punctuation from a sentence. For this you could use this regular expression:
```
[\.:,"'\(\)\[\]|/?!;]+
```
The [regex](https://crates.io/crates/regex) crate uses perl style expressions which is also what java does.
To make this work in rust:
{{<highlight foo>}}
use regex::Regex;
let punct_re = Regex::new(r#"[\d\.:,"'\(\)\[\]|/?!;]+"#).unwrap();
{{</highlight>}}
I did not use highlighting in this snippet, because the highlighter on this page is actually incorrect in that it doesn't 'escape' the double-quote character in the middle, thinking it's the end of the string. The code uses a _raw string_:
`r#"..."#`
If I hadn't included a double-quote as part of the expression, this would have been valid as well:
`r"..."`
And if I wanted to include a pound-sign (#) in the expression, I would need to write this:
`r##"..."##`
This syntax avoids having to use escaping with backslash and makes the expression more readable.
The newly created ```punct_re``` expression can simply be used like this:
{{<highlight rust>}}
let it_contains_punctuation = punct_re.is_match("!");
{{</highlight>}}
Check [the docs](https://docs.rs/regex/latest/regex/) for more information on all available methods.
### Replace all
In our case we need to use ```replace_all``` and pass an empty string to effectively remove all unwanted characters:
{{<highlight rust>}}
let result = punct_re.replace_all("hello world!", "");
{{</highlight>}}
Now it will get a little bit tricky, because `replace_all` does not return a String or string slice, but instead a Cow...
![meuh](/img/patrick-baum-hB9vo06o9z8-unsplash.jpg)
_No not you!_
A COW as in Clone On Write:
```
A clone-on-write smart pointer.
The type Cow is a smart pointer providing clone-on-write functionality: it can enclose and provide immutable access to borrowed data, and clone the data lazily when mutation or ownership is required. The type is designed to work with general borrowed data via the Borrow trait.
Cow implements Deref, which means that you can call non-mutating methods directly on the data it encloses. If mutation is desired, to_mut will obtain a mutable reference to an owned value, cloning if necessary.
```
_What is this and why is it used in `replace_all`?_
To start with the latter, it was put in for efficiency, returning a reference to the original string in case nothing needed replacing. And a `Cow` allows mutation, as opposed to other smart pointers (like `Box` or `Rc`), which is useful when you do need to replace.
If you want you can read more [here](https://github.com/rust-lang/regex/issues/676)
As the docs state:
`Cow` implements `Deref`
Which means that something like the C-language `*` operation for pointers is automatically applied by the compiler to turn the smart pointer to a value, into the value itself.
{{<highlight rust "linenos=table">}}
use std::borrow::Cow;
let result: Cow<str> = punct_re.replace_all("hello world!", "");
let result: &str = &result;
{{</highlight>}}
I have included the types to show what goes on and because line 4 wouldn't compile without it.
1. you get the result as `Cow<str>`
2. you say you want a string slice, so the compiler deref's the `Cow` to `str`.
3. and you get a new reference `&` to `result`.
Without dereferencing you would get a `&Cow<str>` instead, which isn't helpful at all.
One last thing: `let result` twice? Yes, that's rust's [shadowing](https://doc.rust-lang.org/stable/rust-by-example/variable_bindings/scope.html). Really handy to avoid (quasi) hungarian notation.
### constants
Rust has a `const` keyword:
{{<highlight rust>}}
const A: usize = 1;
{{</highlight>}}
But
{{<highlight rust>}}
const punct_re: Regex = Regex::new(r#"[\d\.:,'\(\)\[\]|/?!;]+"#).unwrap();
{{</highlight>}}
is not allowed! Because it contains a function call, so the actual value cannot be determined until after compilation.
To work around this we need [lazy_static](https://crates.io/crates/lazy_static).
This is a `macro` and the code that you put in it is guaranteed to only run once.
We could simply put it in a function, right where we need it:
{{<highlight rust "linenos=table">}}
pub fn clean(text: &str) -> String {
lazy_static! {
static ref PUNCT: Regex = Regex::new(r#"[\d\.:,"'\(\)\[\]|/?!;]+"#).unwrap();
}
String::from(PUNCT.replace_all(text, ""))
}
{{</highlight>}}
(_note that I took the `"` out, and used highlighting again_)
`!important!` I cannot use `&str` here, because returning a reference from a function is in fact a _dangling pointer_. That is a pointer to memory that is owned by the function and reclaimed when it finishes. That's why we have to copy the value to an owned `String` and return that. This has a performance impact. Try to avoid copying as much as possible!
**Conclusion**
Working with regular expressions and constants isn't really difficult, but it opens the door to some more advanced concepts in the rust type system.
I highly recommend https://rust-unofficial.github.io/too-many-lists/index.html. Don't just read it. Don't copy-paste the code. Don't even copy it manually.
_Read it, hide the browser tab, and try to create the code of a variation on the linkedlist by heart._ Reopen the tab whenever you are stuck. And don't despair!

View file

@ -1,7 +1,8 @@
--- ---
title: "Rust for Java developers, part 1" title: "Rust for Java developers, Introduction"
date: 2021-12-17T13:07:49+01:00 date: 2021-12-17T13:07:49+01:00
draft: true draft: false
author: Sander Hautvast
--- ---
![rusty bridge](/img/christopher-burns--mUBrTfsu0A-unsplash.jpg) ![rusty bridge](/img/christopher-burns--mUBrTfsu0A-unsplash.jpg)
@ -16,8 +17,25 @@ And java powers much of the web. Rust, while being regarded as a _systems_ langu
**tl;dr** **tl;dr**
Do not think 'Rust is just a new language, I can map what I already know onto some new keywords'. Rust is different. But taking the effort will pay off. Do not think 'Rust is just a new language, I can map what I already know onto some new keywords'. Rust is different. But taking the effort will pay off.
In this post you will see some syntax and get a feel for how rust differs from other languages because of its implementation of _ownership_.
Imagine a language in which duplicating a valid line of code slaps you in the face with a compilation error: Imagine a language in which duplicating a valid line of code slaps you in the face with a compilation error:
{{< gist shautvast 1e402ea018b1a3209d7e4b1794e93250 >}} {{<highlight rust "linenos=table">}}
fn main() {
let name = String::from("Bob");
let msg = get_hello(name);
println!("{}", msg);
// let msg2 = get_hello(name); -- ERROR Use of moved value
// println!("{}", msg2);
}
fn get_hello(name: String) -> String{
let mut text = "Hello ".to_owned();
text.push_str(&name);
return text;
}
{{</highlight>}}
__line 6 is a copy of line 3. The first compiles but the second does not. WTF?__ __line 6 is a copy of line 3. The first compiles but the second does not. WTF?__
@ -29,14 +47,20 @@ Next `()`, like in a lot of languages means: put any parameters here. Curly brac
`let` defines a variable, in ths case `name`. Some things to say about this: `let` defines a variable, in ths case `name`. Some things to say about this:
For one: rust is _strongly typed_, so we could also say `let name: String = String::from("...")` but rust infers the type as much as possible (and that is much more than let's say java does). For one: rust is _strongly typed_, so we could also say `let name: String = String::from("...")` but rust infers the type as much as possible (and that is much more than let's say java does).
Actually `let` does more than declare and assign, it determines ownership. More on that later. Actually `let` does more than declare and assign, it establishes ownership. More on that later.
One last thing to mention here: One last thing to mention here:
this is allowed: this is allowed:
{{< gist shautvast de41deb52f42ecba42ea14408c1f49fd >}} {{<highlight rust>}}
let name;
name = String::from("<your name here>");
let msg = get_hello(name);
{{</highlight>}}
and this is not: and this is not:
{{<highlight rust>}}
{{< gist shautvast 446e584ee590ed1c2014d4b6bbd48063 >}} let name;
// let msg = get_hello(name); -- ERROR Use of possibly uninitialized variable
{{</highlight>}}
because no value is actually assigned to `name`. In java we could cheat and assign `null` to name, but rust won't have any of that. because no value is actually assigned to `name`. In java we could cheat and assign `null` to name, but rust won't have any of that.
@ -57,60 +81,77 @@ About strings: There is String and there is &str (pronounced string-slice). Java
So rust String is mutable and &str is not. The latter is also a (shared) reference. This is important. It means I have **borrowed** something, I can use it, but I am not allowed to change it and I am certainly not the **owner**. More on ownership later... So rust String is mutable and &str is not. The latter is also a (shared) reference. This is important. It means I have **borrowed** something, I can use it, but I am not allowed to change it and I am certainly not the **owner**. More on ownership later...
On to the next two lines. On to the next two lines.
``` {{<highlight rust "linenos=table,linenostart=3">}}
let msg = get_hello(name); let msg = get_hello(name);
println!("{}", msg); println!("{}", msg);
``` {{</highlight>}}
Nothing too fancy: we call a function and print the result. Rust has easy string interpolation using `{}` for a value that you want to insert into a string. The print! statement ends with an exclamation mark and this signifies that you're dealing with a *macro*. So it compiles to something completely different, but who cares about that? Nothing too fancy: we call a function and print the result. Rust has easy string interpolation using `{}` for a value that you want to insert into a string. The print! statement ends with an exclamation mark and this signifies that you're dealing with a *macro*. So it compiles to something completely different, but who cares about that?
Next: Next:
{{<highlight rust "linenos=table,linenostart=10">}}
`fn get_hello(name: String) -> String {...` fn get_hello(name: String) -> String {
{{</highlight>}}
This function actually has a return value which unlike java and C is at the end of the declaration (following the arrow```->```). I find this more intuitive. This function actually has a return value which unlike java and C is at the end of the declaration (following the arrow```->```). I find this more intuitive.
Next we again create a String, concatenate the input argument and return the result. Nothing too fancy, but... {{<highlight rust "linenos=table,linenostart=11">}}
let mut text = "Hello ".to_owned();
{{</highlight>}}
`let mut some_string = String::from("Hello ");` Here we create a String, concatenate the input argument and return the result. Make sure the test variable is `mut`. The method `to_owned` creates a copy of a str slice in the form of an owned String.
The `mut` keyword indicates that we are actually allowed to change the the value of `some_string`. I mentioned earlier that String is mutable, but if you actually want to mutate it, you have to add `mut`. And: **types aren't mutable (or not), bindings are!** The `mut` keyword indicates that we are actually allowed to change the the value of `some_string`. I mentioned earlier that String is mutable, but if you actually want to mutate it, you have to add `mut`. And: **values aren't mutable (or not), bindings are!**
So: So:
``` {{<highlight rust>}}
let x = String::from("x"); let x = String::from("x");
//x.push_str("NO!"); // compilation error, x is not mutable //x.push_str("NO!"); // compilation error, x is not mutable
let mut y = x; let mut y = x;
y.push_str("YES!"); // but this compiles! y.push_str("YES!"); // but this compiles!
``` {{</highlight>}}
Weird, right? Well remember that by default nothing is mutable. And that things can magically 'become' mutable through `mut`. Weird, right? Well remember that by default nothing is mutable. And that things can magically 'become' mutable through `mut`.
But be aware: What do you think would happen if you'd add But be aware: What do you think would happen if you'd add
`get_hello(x);`? `get_hello(x);`?
It would not compile. Ownership has passed from `x` to `y`. So there is never a situation in which a value is mutable and immutable at the same time. The compiler prevents this. And it prevents a **lot** of things. The rust compiler is quite famous for handing you friendly reminders that your code has issues, even suggesting fixes... It would not compile. Ownership has passed from `x` to `y`. Now, sit back and just let this sink into your mind...
Still here? After ```y=x```, ```x``` can no longer be accessed!
So in this case, there is never a situation in which a value is mutable and immutable at the same time. The compiler won't have it. This way it prevents a **lot** of bugs. The rust compiler is quite famous for handing you friendly reminders that your code has issues, even suggesting the fix...
Next line: Next line:
`some_string.push_str(&name);` {{<highlight rust "linenos=table,linenostart=12">}}
text.push_str(&name);
{{</highlight>}}
What's interesting here is that we pass a reference to `name`, using the ampersand `&`. A reference to a String is by definition a string-slice. So the signature for push_str is: What's interesting here is that we pass a reference to `name`, using the ampersand `&`. A reference to a String is by definition a string-slice. So the signature for push_str is:
`pub fn push_str(&mut self, string: &str)` {{<highlight rust>}}
pub fn push_str(&mut self, string: &str)`
{{</highlight>}}
Oh and this is interesting: the `self` keyword is sort of like `this` in java, but not quite. See `this` is the reference to the object you act on in java. In rust, like python, if we have a function on an object (also called a method), the first argument is always `self`. We have objects in rust, but they're called `struct`, and they're like classes and objects really, but there's no inheritance. Remember 'favour composition over inheritance'? Oh and this is interesting: the `self` keyword is sort of like `this` in java, but not quite. See `this` is the reference to the object you act on in java. In rust, like python, if we have a function on an object (also called a method), the first argument is always `self`.
There are objects in rust, but they're called `struct`, and they are similar in some respects, but there's no inheritance. Remember: _favour composition over inheritance_.
Okay, one more line to go: Okay, one more line to go:
{{<highlight rust "linenos=table,linenostart=13">}}
`return some_string;` return text;
{{</highlight>}}
You might have noticed that this could have been java code, except that rust prefers _snake_case_ for local variables. The compiler actually enforces this. You can turn it off, but that would be rude. You might have noticed that this could have been java code, except that rust prefers _snake_case_ for local variables. The compiler actually enforces this. You can turn it off, but that would be rude.
The funny thing about this line is that in practice it will always be written like this: The funny thing about this line is that in practice it will always be written like this:
`some_string` {{<highlight rust "linenos=table,linenostart=13">}}
text
{{</highlight>}}
`return` is optional (handy for early returns), but it needs the semicolon. It's shorter without them, so why not? `return` is optional (handy for early returns), but it needs the semicolon. It's shorter without them, so why not?
Be aware that is more powerful than it seems at first glance. The last line(s) of anything, provided that is does not end with `;` is always evaluated as an expression and returned to the caller. So it could have been a compound `match` expression (fetaured in a later blog) or a simple `if` (which is also an expression!): Be aware that is more powerful than it seems at first glance. The last line(s) of anything, provided that is does not end with `;` is always evaluated as an expression and returned to the caller. So it could have been a compound `match` expression (fetaured in a later blog) or a simple `if` (which is also an expression!):
```
{{<highlight rust>}}
fn foo(bar: bool) -> u32{ fn foo(bar: bool) -> u32{
if bar { if bar {
1 1
@ -118,7 +159,8 @@ fn foo(bar: bool) -> u32{
0 0
} }
} }
``` {{</highlight>}}
This is so much cleaner than anything else! This is so much cleaner than anything else!
__ownership__ __ownership__
@ -135,3 +177,6 @@ I saved the best for last here, but I have already given some hints as to what t
The latter is the reason that I could not call `get_hello` *twice* with `name` as the argument. At the first call, my ownership is passed on. The function, being the owner, can do with as it sees fit, but at the end of the function, the the value is dropped and you can no longer use it. Once you have updated your mental models with this knowledge, your life as a rust developer has become a little easier. The latter is the reason that I could not call `get_hello` *twice* with `name` as the argument. At the first call, my ownership is passed on. The function, being the owner, can do with as it sees fit, but at the end of the function, the the value is dropped and you can no longer use it. Once you have updated your mental models with this knowledge, your life as a rust developer has become a little easier.
Astute readers may have noticed the `&mut` before `self` in the function declaration for push_str. This is what a unique reference looks like. So you get a mutable reference to self, which means that you can mutate `self` (here append some text to the string), but you have not become the owner. The mutable reference to self goes out out scope once the method ends and the original owner can carry on using it. This is true for any `&mut`. Astute readers may have noticed the `&mut` before `self` in the function declaration for push_str. This is what a unique reference looks like. So you get a mutable reference to self, which means that you can mutate `self` (here append some text to the string), but you have not become the owner. The mutable reference to self goes out out scope once the method ends and the original owner can carry on using it. This is true for any `&mut`.
If you have made it this far, I recommend you start reading [the book](https://doc.rust-lang.org/book/)
and install [rustup](https://www.rust-lang.org/tools/install). Enjoy!

View file

@ -2,6 +2,7 @@
title: "To Stored Procedure or Not" title: "To Stored Procedure or Not"
date: 2021-12-07T20:32:49+01:00 date: 2021-12-07T20:32:49+01:00
draft: false draft: false
author: Sander Hautvast
--- ---
### Or: My personal Object-Relational Mapping [Vietnam war story](https://blogs.tedneward.com/post/the-vietnam-of-computer-science/) ### Or: My personal Object-Relational Mapping [Vietnam war story](https://blogs.tedneward.com/post/the-vietnam-of-computer-science/)

View file

@ -1,29 +1,36 @@
--- ---
title: "The rust option" title: "The rust option"
date: 2021-12-20T10:55:14+01:00 date: 2021-12-20T10:55:14+01:00
draft: true draft: false
author: Sander Hautvast
--- ---
![choose](/img/vladislav-babienko-KTpSVEcU0XU-unsplash.jpg)
`Option` is a well known construct in many languages. The first time I saw it was in scala, but soon after it appeared in java8. As java still means my livelyhood, it serves as my main frame of reference. I won't be covering all the details, because the java type is really a no brainer: it simply wraps a value _or not_ and what operations (methods on Optional) do depends on there being a value or not. `Option` is a well known construct in many languages. The first time I saw it was in scala, but soon after it appeared in java8. As java still means my livelyhood, it serves as my main frame of reference. I won't be covering all the details, because the java type is really a no brainer: it simply wraps a value _or nothing_ and what operations (methods on Optional) do depends on there being a value or not.
Rust Option starts with the same premisse. But immediately things start to diverge where it comes to actally using it. In rust the type is more or less mandatory for any optional value, because there is no (safe) `null` reference. So any struct or function that allows the absence of a value may use `std::option::Option` (or just `Option`) whereas in java it's (strangely) discouraged by static code analysis tools for class members and method arguments. Just use `null` they say, but I find it very useful to express the explicit possibility of null values as a valid state.
In a java application that uses IOC `null` is in many instances a 'not yet valid state' ie. a class instance has been constructed but its dependencies have not yet been injected. This is never a valid state once the application is fully constructed and so this would not be a case for an Optional value. Rust Option starts with the same premisse. But immediately things start to diverge when it comes to actually using it. In rust the type is more or less mandatory for any optional value, because there is no (safe) `null` reference. So any struct or function that allows the absence of a value may use `std::option::Option` (or just `Option`) whereas in java it's (strangely) discouraged by static code analysis tools for class members and method arguments. Just use `null` they say, but I find it very useful to express the explicit possibility of null values as a valid state.
Rust's Option differs from java's Optional first and foremost in that in rust it is en enum. That would not be possible in java, because it's enums are limited in that an enum in java can only have attributes of the same type (for all enum values). Whereas in rust Option::None is different from Option::Some in that Some has a value ie Some("string") and None has not. In a java application that uses _Dependency Injection_, `null` is in many instances a 'not yet valid state' ie. a class instance has been constructed but its dependencies have not yet been injected. This is never a valid state once the application is fully constructed and so this would not be a case for an Optional value. In java.
Rust's Option differs from java's Optional first and foremost in that in rust it is an _enum_. That would not be possible in java, because java enums are limited. In java enums can only have attributes of the same type (for all variants). Whereas in rust Option::None is different from Option::Some in that Some has a value ie Some("string") and None has not.
In code: In code:
``` {{<highlight rust>}}
pub enum Option<T> { pub enum Option<T> {
None, None,
Some(T), Some(T),
} }
``` {{</highlight>}}
Now you might say: _Option{value}_ is more expensive than '_a value or null_' because you have to wrap the value which costs more storage. For this rust has a trick up its sleeve, called _null pointer optimization_ that eliminates the extra memory use.
Read all about this (and more) [here](https://rust-unofficial.github.io/too-many-lists/first-layout.html).
construction: construction:
|java.util.Optional<T>|std::option:Option<T>| |java.util.Optional<T> |Option<T>|example: Option\<u32>|
|---------------------|---------------------| |------------------------------|----------------|--------------|
| of(T value) | Some(value: T) | | Optional.of(T value) | Some(value: T) | Some(1) |
| ofNullable(T value) | Some(value: T) | | Optional.ofNullable(T value) | - | - |
* Some(null) will not compile. | Optional.empty() | None | None |

View file

@ -2,6 +2,7 @@
title: "Let's create an app in webassembly" title: "Let's create an app in webassembly"
date: 2022-02-05T20:11:08+01:00 date: 2022-02-05T20:11:08+01:00
draft: false draft: false
author: Sander Hautvast
--- ---
![soup assembly](/img/markus-winkler-08aic3qPcag-unsplash.jpg) ![soup assembly](/img/markus-winkler-08aic3qPcag-unsplash.jpg)
Web assembly is cool and new and it lacks nice how-to's for tasks that are by now quite mundane for most web developers. So let's get started using [yew](https://yew.rs/). Web assembly is cool and new and it lacks nice how-to's for tasks that are by now quite mundane for most web developers. So let's get started using [yew](https://yew.rs/).
@ -176,7 +177,7 @@ So DropImage is now a _Component_, much the same way as in for instance _Angular
There are some differences with regular html to be aware of. All _text_ must be surrounded by curly braces {}. A constant string in quotes will simply be turned into the text html child, but you can output any component value, eg: ```{self.value}``` There are some differences with regular html to be aware of. All _text_ must be surrounded by curly braces {}. A constant string in quotes will simply be turned into the text html child, but you can output any component value, eg: ```{self.value}```
Note that two event handlers, ```ondragover``` and ```ondrop``` are registered in the drop-zone div. What does ```{link.callback(|e| Msg::Dragged(e))}``` mean? It sends a message called Msg::Dragged with a payload that is the raised html event (e). The component now be able the handle this message. For this you need: Note that two event handlers, ```ondragover``` and ```ondrop``` are registered in the drop-zone div. What does ```{link.callback(|e| Msg::Dragged(e))}``` mean? It sends a message called Msg::Dragged with a payload that is the raised html event (e). The component is now be able the handle this message. For this you need:
```update``` is called by the framework and it receives an instance of the Msg enum and it will respond by choosing appropriate action. This could mean update the internal component state or the view directly. I fact I doubt if the latter is really what you would want. In fact we could have defined the _images_ div as follows ```update``` is called by the framework and it receives an instance of the Msg enum and it will respond by choosing appropriate action. This could mean update the internal component state or the view directly. I fact I doubt if the latter is really what you would want. In fact we could have defined the _images_ div as follows
{{<highlight html>}} {{<highlight html>}}

View file

@ -2,6 +2,7 @@
title: "On the value of software and pasta" title: "On the value of software and pasta"
date: 2022-01-17T10:29:29+01:00 date: 2022-01-17T10:29:29+01:00
draft: false draft: false
author: Sander Hautvast
--- ---
![cash](/img/pradamas-gifarry-bVfMuhN9w6I-unsplash.jpg) ![cash](/img/pradamas-gifarry-bVfMuhN9w6I-unsplash.jpg)
This week some things 'happened' to me that made me want to read up on the grander scheme of things, meaning economic value, in particular that of software. I'll just list them briefly This week some things 'happened' to me that made me want to read up on the grander scheme of things, meaning economic value, in particular that of software. I'll just list them briefly
@ -67,7 +68,7 @@ Start simple. Improve later
Maybe the pasta makers at Felicetti also started with a plain staple pasta that did not stand out from anything in the supermarket. Their factory and lab are immensely impressive but it started a hundred years ago when the great grandfater sold his company and bought a small pasta factory. Maybe the pasta makers at Felicetti also started with a plain staple pasta that did not stand out from anything in the supermarket. Their factory and lab are immensely impressive but it started a hundred years ago when the great grandfater sold his company and bought a small pasta factory.
Pasta is a commodity. But achieving _this_ product, sold at _this_ price (3 euros for 500 g, which is expensive, but not extremely so) ensures that the whole supply chain gets the correct price. 'from the seed to the land, to the farmer, to the miller, the pasta producer and the owner of the store.' Pasta is a commodity. But achieving _this_ product, sold at _this_ price (3 euros for 500 g, which is expensive, but not extremely so) ensures that the whole supply chain gets the correct price. 'from the seed to the land, to the farmer, to the miller, the pasta producer and the owner of the store.' (Riccardo Felicetti).
What he says next (3:10) is nothing less than mind blowing: What he says next (3:10) is nothing less than mind blowing:

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB