The fundamentals of the languages.
We are going to go over some fundamentals of each language to hopefully make the transitioning easier between these 3 languages.
Remember, this is fast paced, but there is room for questions, as I have even made explicit stops. If you are viewing this live on FrontEndMasters.com or twitch.tv/ThePrimeagen and have a question, please feel free to throw it in the chat and hopefully I can answer it!
Syntax
I am not going to cover language specifics and syntax.
Example: what happens here? If you have some experience with rust, please don't answer.
let foo = if boolean_statement {
0
} else {
1
};
TypeScript version
const foo = boolean_statement ? 0 : 1;
So if you do have a question, speak up during those times and I'll go over anything specific. But the goal here is to make a course that is geared towards people who feel comfortable with programming and would like to pick up a second or third language.
Rust Borrow Checker
Rust is famous for difficulty. For its borrow checker. There are memes and I personally have rage quit using rust because I didn't understand the basics of rust.
From the rearview mirror, this is simple
Lets start with other programming languages
JavaScript
const a = [];
const b = a;
b.push(5);
console.log(a);
console.log(b);
C++
std::vector<int> a;
std::vector<int> b = a;
b.push_back(5);
printf("a size: %zu\n", a.size());
printf("b size: %zu\n", b.size());
Rust
let a: Vec<i32> = vec![];
let mut b = a;
b.push(5);
println!("a size: {}", a.len());
println!("b size: {}", b.len());
The three types
- Value
- Reference
- Mutable Reference
The three simple rules
Rule #1: Value
Only one value owner.
Note
If the object implements copy, it can be implicitly copied
let x = 5;
let y = x;
println!("{}", x + y);
Rule #2: Reference
You can have as many references as you like with the constraint that there are no mutable references alive.
let x = 5;
let y = &x;
println!("here is {} and {}", x, y);
Rule #3:1 Mut Reference
You can have one mut reference and no reference at the same time.
fn main() {
let mut x = 5;
let y = &x;
let z = &mut x; // cannot borrow x as mutable
// because its already as immutable
println!("{}", x + y + z);
}
Questions?
Moar things to cover
- Rust + Enums (Options)
- Options
- Error Handling
- Results
- Testing
Enums
Lets look at emuns in typescript, go, and rust.
First typescript, lets program up a quick example.
I should have typed something like this.
enum Thing {
Foo = 1, // or give them no value and it will start at 0 and increment
Bar = 2,
Baz = 3,
}
What about go?
Onto another example!
Code
type Foo = int
const (
Thing Foo = iota
Other
That
)
Now Rust
Or is this typescirpt?
enum Thing {
Foo, // or give them no value and it will start at 0 and increment
Bar,
Baz,
}
Why go over enums...
They are simple constructs. Well, they are simple in other languages.
Lets dive more into them with rust. Let me show you how you can add types...
What my code should of approximately looked like
enum Option2 {
Baz,
Foo(isize),
Bar(String),
Fuzz(Vec<String>), // string[], or a []string
}
fn main() {
let opt2 = Option2::Foo(5);
let mut opt22 = Option2::Fuzz(vec![]);
if let Option2::Foo(x) = opt2 {
let _ = x + 5;
// x = 7;
}
if let Option2::Fuzz(vec) = &mut opt22 {
vec.push(String::from("Hello, world!"));
}
match opt2 {
Option2::Baz => todo!(),
Option2::Foo(_) => todo!(),
Option2::Bar(_) => todo!(),
Option2::Fuzz(_) => todo!(),
}
}
What does this mean?
This means that you can have an enum with many types, and these types can be generic.
enum Foo<T> {
Bar(T)
}
But how is this practically useful?
3 things.
- lists with many types
- Nullable
- Error Handling
Lets start with nullable and TypeScript
I think we have all seen code like this
type Foo = {
bar?: number;
}
function test(foo: Foo) {
if (foo.bar) { // this is annoying, yes
// undang
} else {
// dang
}
}
Let me show you nullables in Rust
These are Options, they are enums, they have a generic.
enum Option<T> {
None,
Some(T)
}
Lets see how you can play with these in Rust
Potential code for options
struct Foo {
bar: Option<i32>
}
fn main() {
let foo = Foo {
bar: None
};
let foo2 = Foo {
bar: Some(2)
};
if foo.bar.is_some() {
let sum = foo.bar.unwrap() + 5;
}
foo.bar.unwrap_or(0);
foo.bar.unwrap_or_else(|| {
return 5;
});
let out = foo.bar.map(|x| {
return x + 5;
});
}
Questions so far?
Lets implement the Option enum!
For fun lets try to implement map
and is_some
in rust on our "option" type.
Results of this
enum Option2<T> {
None,
Some(T)
}
impl<T> Option2<T> {
pub fn map(&self, f: fn(&T) -> T) -> Option2<T> {
return match self {
Option2::None => Option2::None,
Option2::Some(v) => Option2::Some(f(v)),
}
}
pub fn is_some(&self) -> bool {
return match self {
Option2::None => false,
Option2::Some(_) => true,
}
}
}
fn main() {
let opt = Some(5);
let opt2 = Option2::Some(5);
opt.map(|x| x + 5);
let opt2 = opt2.map(|x| x + 5);
if opt2.is_some() {
}
}
Questions? That was pretty radical section
Hopefully you can see the incredible value of sumtypes.
There is this concept in 1984 that peoples ability to think is directly tied with the language they communicate with. I think this is true in programming as well. This is one of the reasons learning a ton of languages is REALLY beneficial.
What about error handling?
JavaScript (TypeScript)
"Exceptions as control flow"
2 types of errors that you will run across.
- returned errors
- thrown errors
Pretty classic javascriptism -- conflation issues
Let me program you a live example!
Code
function foo() {
throw new Error("Goodbye, World");
}
try {
foo();
} catch (e) {
console.log("We had a problem, but we are ok", e);
}
console.log("great success()");
In general TypeScript uses exceptions for control flow and with promises its a
mix of value vs throwing due to .catch
.
not all errors can be caught and will just simply blow up somewhere...
Lets look at Go
We haven't done much of go, but it does differ here from typescript.
This is one of the most fundamental arguments against and for go is its error handling. I will say that the error handling i find better than typescript but definitely more boilerplate to deal with it.
The reason why i like it is because of control flow and where things can go wrong.
Example time!
Remember, errors are just values
- create error
- return error + struct
The go code
package main
import (
"errors"
"fmt"
)
func example() error {
return fmt.Errorf("here is an error with a string");
}
func otherExample() error {
return errors.New("here is an error, but with errors") // approx same thing
}
// errors are pointers under the hood, so you can return the empty type
func exampleNoError() error {
return nil;
}
type Thing struct { }
func exampleWithData(should bool) (*Thing, error) {
if should {
return &Thing{}, nil
}
return nil, fmt.Errorf("nice try, guy")
}
func main() {
err := example();
if err != nil {
// handle error
}
_, err = exampleWithData(true)
if err != nil {
// handle error
}
}
Rust
Remember those enums (sumtypes) and how I told you they handled errors? Well,
here is the Result
type.
type Result<V, E> {
Err(E),
Ok(V)
}
Lets make some examples of how to use them!
The code I wrote on a sunday morning
I am sure there is a Johnny Cash reference somewhere around here.
fn error(num: i32) -> Result<(), usize> {
if num < 0 {
return Err((num * -1) as usize);
}
return Ok(());
}
fn main() -> Result<(), usize> {
let res = error(5);
if res.is_ok() {
//...
}
match res {
Err(e) => // ...
Ok(v) => // ...
}
let x = res.unwrap_or(());
let x = res.expect("THIS BETTER EXIST");
let x = res.unwrap(); // BAD
let x = res?;
return Ok(());
}
Anyhow?
A nice library for writing great code with error handling is anyhow! Lets look at it
Unit Testing!
- TypeScript : Cries in Configuration
- GoLang : Meh
- Rust : oyes
src/__tests__/test.ts
test("foo", function() {
expect("foo").toEqual("foo");
});
yarn add jest ts-jest @types/jest
npx jest
Go version
there is some contention with how / where to put your tests.
- test public interfaces only
- test within the package
pkg/name/file.go
pkg/name/file_test.go
package name_test
import "testing"
func TestThisFunc(t *testing.T) {
this := 5
if this != 7 {
t.Errorf("expected %v to equal 7", this)
}
}
go test ./...
go test ./path/to/package
Go does have an assertion library
But we will not be using it. We will just use what is built in during this course.
Rust Version
Rust, of course, is the best
- test in file
- One thing to be careful of is to what level private interfaces should be tested.
... // code ...
#[cfg(test)]
mod test {
#[test]
fn this_test() {
assert_eq!(5, 7);
}
}
cargo test
cargo test path/to/file.rs
You will forget everything i just said
That is ok. The best way to make it set? Build it.