Add blog post (en)
This commit is contained in:
parent
d7349bca3a
commit
e2cc0119ad
BIN
public/img/blog/en/langs/cover.jpg
Normal file
BIN
public/img/blog/en/langs/cover.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
public/img/blog/en/langs/sparks_joy.jpg
Normal file
BIN
public/img/blog/en/langs/sparks_joy.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
323
src/pages/blog/en/java-and-rust.md
Normal file
323
src/pages/blog/en/java-and-rust.md
Normal file
@ -0,0 +1,323 @@
|
||||
---
|
||||
layout: ../../../layouts/BlogLayout.astro
|
||||
title: Rust candy and Java vegetables
|
||||
description: |
|
||||
I diskile Java/Go's verbosity. I like Rust/Zig
|
||||
syntax sugar and semantics.
|
||||
pubDate: "2024-07-26"
|
||||
tags: ["tech", "languajes", "java", "rust", "go", "verbosity"]
|
||||
image:
|
||||
url: "/img/blog/en/langs/cover.jpg"
|
||||
alt: "Image of a kid eating sweets and other eating vegetables."
|
||||
caption: "Images from Irasutoya."
|
||||
---
|
||||
|
||||
These may sound like first world problems, but I dislike working
|
||||
with Java. The main reason: syntax, verbosity, semantics.
|
||||
|
||||
See, in rust I can just do `Some(value)` and wherever I see
|
||||
the `Option<Type>` signature, I know that a value can be null.
|
||||
|
||||
Meanwhile in Java you can't tell. You just can't. Everything
|
||||
can be null. You either go read the friendly manual
|
||||
or get a `NullPointerException`. There is no in-between when
|
||||
encountering an API for the first time.
|
||||
|
||||
And the pattern continues everywhere.
|
||||
|
||||
|
||||
## Errors
|
||||
|
||||
|
||||
So what if I want to signal that a function may return an error?
|
||||
In Rust I just return `Result<OkType, ErrorType>` and
|
||||
use pattern matching or bubble up the error.
|
||||
|
||||
```rust
|
||||
fn next_token(chars: Vec<char>) -> Result<Token, Error> {
|
||||
// Bubble up the error easily
|
||||
let next_token = possibly_errors(chars)?;
|
||||
|
||||
// Handle the error, return a different error
|
||||
// or recover
|
||||
let next_token = match possibly_errors(chars) {
|
||||
Ok(t) => t,
|
||||
Error(Error::EOF) => Token::EOF,
|
||||
Error(e) => return e,
|
||||
};
|
||||
|
||||
// continue...
|
||||
}
|
||||
```
|
||||
|
||||
I can see just from the function signature that it may fail, which
|
||||
error cases there are, and it's easy to handle or bubble.
|
||||
|
||||
|
||||
So what about Java? Well, it depends.
|
||||
|
||||
If we are using checked exceptions (all my homies hate checked exceptions)
|
||||
(we don't use checked exceptions) we can see all the `throws` in the
|
||||
signature and the compiler enforces proper handling of those.
|
||||
|
||||
So, how do we bubble up the exception?
|
||||
|
||||
```java
|
||||
protected Token nextToken(ArrayList<Char> chars)
|
||||
throws EndOfFileException, LexException
|
||||
{
|
||||
// Bubble up
|
||||
Token nextToken;
|
||||
try {
|
||||
nextToken = possiblyThrows(chars);
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
// Handle the error, return a different error
|
||||
// or recover
|
||||
Token nextToken;
|
||||
try {
|
||||
nextToken = possiblyThrows(chars);
|
||||
} catch (EndOfFileException e) {
|
||||
nextToken = new Token::EOF();
|
||||
} catch (LexException e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// continue...
|
||||
}
|
||||
```
|
||||
|
||||
It's slow to write and think about. It breaks the flow of
|
||||
programming. I've just come up with the solution to the
|
||||
problem I'm having and I'm writing as fast as I can, and
|
||||
then I'm hit with a wall of verbosity. I've lost momentum.
|
||||
The excitement of programming is gone. All that remains
|
||||
is boring, repetitive keywords and operators.
|
||||
|
||||
And that's the optimistic case. Because nobody writes code
|
||||
like above. Everybody wraps everything in a giant try-catch.
|
||||
Context is lost. Flow is lost.
|
||||
|
||||
And even worse, nobody likes nor uses checked exceptions.
|
||||
Java programmers will tell you that checked exceptions are
|
||||
bad. Just Google "java checked vs unchecked exceptions".
|
||||
|
||||
So what really happens is you write your code, and because
|
||||
everybody uses `RuntimeException` you don't get an error
|
||||
in your editor because you are calling something that may
|
||||
fail. You just let it break.
|
||||
|
||||
```java
|
||||
protected Token nextToken(ArrayList<Char> chars)
|
||||
throws RuntimeException
|
||||
{
|
||||
// No errors detected, YOLO
|
||||
Token nextToken = possiblyThrows(chars);
|
||||
|
||||
// continue...
|
||||
}
|
||||
```
|
||||
|
||||
Then your program blows up at runtime, and you have
|
||||
to read a 10 page long stack trace to find where it
|
||||
chrashed and why.
|
||||
|
||||
See, you need "discipline" and "good practices" to write
|
||||
effective Java code. The compiler can't enforce those,
|
||||
what are you talking about? And what if, as a human
|
||||
being, you forget to add the boilerplate once, and your
|
||||
program blows up in testing? Skill issue. Just wrap
|
||||
`main()` in a giant try-catch.
|
||||
|
||||
So, what other candy does Rust offer that is too
|
||||
1000 iq for us mortals below the Rust wizards?
|
||||
|
||||
|
||||
## Proper flow control
|
||||
|
||||
Say I want to assing a value to a variable if some
|
||||
condition is met.
|
||||
|
||||
```java
|
||||
Type variable = (condition)? value1 : value2;
|
||||
```
|
||||
|
||||
Now what if I want to operate on `value1`
|
||||
before assigning it?
|
||||
|
||||
```java
|
||||
Type variable;
|
||||
if (condition) {
|
||||
// operations with `value1`
|
||||
variable = value1;
|
||||
} else {
|
||||
variable = value2;
|
||||
}
|
||||
```
|
||||
|
||||
What if I have multiple conditions?
|
||||
|
||||
```java
|
||||
String result;
|
||||
switch (someValue) {
|
||||
case "A": {
|
||||
// other things
|
||||
result = "a";
|
||||
break;
|
||||
}
|
||||
case "B": {
|
||||
// other things
|
||||
result = "b";
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Oops, I forgot `default`. Now I have a null pointer
|
||||
going around, breaking things. Luckily I didn't
|
||||
forget to scope all cases and use `break` tho.
|
||||
The compiler doesn't care. It's a skill issue.
|
||||
|
||||
And Rust?
|
||||
|
||||
```rust
|
||||
// Conditionals are expressions
|
||||
let variable = if condition {
|
||||
// operations with value1
|
||||
value1
|
||||
} else {
|
||||
value2
|
||||
};
|
||||
|
||||
// Pattern matching
|
||||
let result: String = match some_value {
|
||||
"A" => {
|
||||
// other things
|
||||
"a"
|
||||
}
|
||||
"B" => {
|
||||
// Other things
|
||||
"b"
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Dang. I forgot the default case again.
|
||||
The compiler will yell at me, tell me that
|
||||
I'm missing a case. It will not compile until
|
||||
the code is covered.
|
||||
|
||||
And later on, if I add some new cases, the code
|
||||
will break again if I don't handle those new cases
|
||||
everywhere. Java would silently fail and let my
|
||||
program crash at runtime.
|
||||
|
||||
|
||||
## The small things
|
||||
|
||||
There are a lot of small things that Rust do that Java
|
||||
doesn't.
|
||||
|
||||
- Sum types
|
||||
- Pattern matching
|
||||
- Conditionals as expressions
|
||||
- Proper type inference
|
||||
- Blocks of code as expressions
|
||||
- Immutability
|
||||
- Borrow checking
|
||||
- Traits and structs
|
||||
- Hygienic macros
|
||||
- #\[derive] auto implementation
|
||||
- Cargo
|
||||
- Crates
|
||||
- rust analyzer
|
||||
- Documentation comments
|
||||
- Different syntax for values and pointers
|
||||
- Unsafe when neccesary
|
||||
|
||||
All those things add up to create a good experience
|
||||
when programming. All those clear obstacles when
|
||||
programming.
|
||||
|
||||
And a lot can be said about every one of these,
|
||||
but I think you get the point.
|
||||
|
||||
|
||||
## Skill issue detected!!!1!1!!
|
||||
|
||||
**SKILL ISSUE** is a good meme, but really it doesn't make
|
||||
any sense anywhere when used unironically. Because when
|
||||
taken to it's logical conclusion we should all be living
|
||||
in the caverns bangings rocks for fun.
|
||||
|
||||
A Java dev will say: Oh, you can't write boilerplate
|
||||
and check every place it may fail? Skill issue.
|
||||
|
||||
Then a C++ dev will say: Oh, you can't write code
|
||||
for different platforms at the same time? Skill issue.
|
||||
|
||||
Then a C dev will say: Oh, you can't write code without
|
||||
your little classes and templates? Skill issue.
|
||||
|
||||
Then an assembly dev will say: Oh, you can't write
|
||||
instructions for every instruction set and need
|
||||
portability? Skill issue.
|
||||
|
||||
Then an machine code dev will say: Oh, you can't just
|
||||
memorize every instruction and need little mnemonics?
|
||||
Skill issue.
|
||||
|
||||
Then a punchcard machine operator will say: Oh, you can't
|
||||
just write your program first try without making any
|
||||
mistake? Skill issue.
|
||||
|
||||
Then an oficinist will say: Oh, you can't just do all the
|
||||
calculations on paper and need a machine to do them
|
||||
for you? Skill issue.
|
||||
|
||||
Then a farmer will say: Oh, you can't just keep track of all
|
||||
your assets and do the calculations in your head? Skill issue.
|
||||
|
||||
Then a gatherer will say: Oh, you can't just gather the food
|
||||
you need and leave the soil do its thing? Skill issue.
|
||||
|
||||
Then a caveman will say: Oh, you can't just outchase a leopardd
|
||||
and kill a deer for food? Skill issue.
|
||||
|
||||
And so on until all we do is survive for the sake of it.
|
||||
Improvements people, we all crave them yet ridiculize them
|
||||
if they don't belong to our camp.
|
||||
|
||||
|
||||
## If Java is like vegetables, then Java is good?
|
||||
|
||||
Yes if by good you mean you can pay the bills.
|
||||
|
||||
|
||||
|
||||
## The joy of programming
|
||||
|
||||
![Rust sparks joy, Java doesn't spark joy](/img/blog/en/langs/sparks_joy.jpg)
|
||||
|
||||
It's been said a lot of times by a lot of people. Programming
|
||||
is about solving problems, not about writing little
|
||||
colored words. And if your motivation for writing code is
|
||||
money, sure, this doesn't matter to you at all. You'd do
|
||||
whatever it takes to make money, and that's okay.
|
||||
|
||||
But if your motivation is the satisfaction you get from
|
||||
solving problems, Java and it's verbosity gets in the
|
||||
way. Unnecesarily slows things down. Breaks the flow.
|
||||
Turns the joy of solving problems into writing
|
||||
little colored words half the time.
|
||||
|
||||
Rust is by no means perfect. It has many issues. But
|
||||
when learned it allows you to write code at the speed
|
||||
of your thougths, and does it so in a safe way,
|
||||
upfront, no surprises.
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user