Skip to content

Commit 611355c

Browse files
authored
Merge pull request #51 from extendr/dev
Add intro to Rust
2 parents 5156e98 + 3377af4 commit 611355c

20 files changed

+2104
-7
lines changed

_quarto.yml

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@ website:
1212
repo-actions: [edit, issue]
1313

1414
navbar:
15-
logo: /images/ferRis.png
15+
logo: /images/feRris.png
1616
left:
1717
- text: "Get Started"
1818
href: get-started.qmd
19-
- text: "User Guide"
20-
href: user-guide/index.qmd
21-
- text: "API"
22-
href: https://extendr.github.io/extendr/extendr_api/
19+
- text: Documentation
20+
menu:
21+
- text: "Rust crate docs"
22+
href: "https://extendr.github.io/extendr/extendr_api/"
23+
- text: "rextendr package"
24+
href: https://extendr.github.io/rextendr/dev/
25+
- user-guide/index.qmd
26+
- intro-rust/index.qmd
2327
- text: "Blog"
2428
href: blog/index.qmd
2529
right:
@@ -29,6 +33,28 @@ website:
2933
href: https://discord.gg/7hmApuc
3034

3135
sidebar:
36+
- title: "Rust for R Developers"
37+
style: "floating"
38+
collapse-level: 1
39+
contents:
40+
- intro-rust/index.qmd
41+
- intro-rust/why-rust.qmd
42+
- intro-rust/hello-world.qmd
43+
- intro-rust/types.qmd
44+
- intro-rust/fizz-buzz.qmd
45+
- intro-rust/collections.qmd
46+
- intro-rust/for-loops.qmd
47+
- intro-rust/mutability.qmd
48+
- intro-rust/mutable-vectors.qmd
49+
- intro-rust/is-odd.qmd
50+
- intro-rust/references-slices.qmd
51+
- intro-rust/iterators.qmd
52+
- intro-rust/iter-map.qmd
53+
- intro-rust/structs.qmd
54+
- intro-rust/struct-methods.qmd
55+
- intro-rust/enums.qmd
56+
- intro-rust/options.qmd
57+
3258
- title: "User Guide"
3359
collapse-level: 1
3460
style: "floating"
@@ -62,7 +88,7 @@ format:
6288
html:
6389
html-table-processing: none
6490
theme: css/_bootswatch.scss
65-
toc: false
91+
toc: true
6692
include-in-header:
6793
- text: |
6894
<script type="text/javascript" src="/js/breadcrumb-fix.js"></script>

get-started.qmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: "Get Started"
3-
anchor-sections: false
3+
toc: false
44
---
55

66
To build R packages with **extendr**, you need to have the right tools.

intro-rust/collections.qmd

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Arrays and Vectors
2+
3+
::: callout-tip
4+
5+
## Objective
6+
7+
Learn how to store multiple values of the same type in arrays and vectors. Understand the difference between arrays and vectors.
8+
:::
9+
10+
11+
## Arrays
12+
13+
Arrays in Rust are fixed in size and hold values of the same type. Since the size is known ahead of time, it makes them fast but inflexible.
14+
15+
```rust
16+
fn main() {
17+
let arr = [10, 20, 30, 40];
18+
println!("Array: {:?}", arr);
19+
}
20+
```
21+
22+
::: callout-note
23+
The `{:?}` syntax is used for a [**Debug**](https://doc.rust-lang.org/std/fmt/trait.Debug.html) representation of a variable. Using `{}` is used for [**Displaying**](https://doc.rust-lang.org/std/fmt/trait.Display.html) data.
24+
25+
More often than not, using `{:?}` will be your best option.
26+
:::
27+
28+
* Arrays use square brackets: `[1, 2, 3]`
29+
* Their size is known at compile time.
30+
* You can't add or remove elements.
31+
* Mostly used when performance is critical and size is known.
32+
33+
The type of an array is specified as `[type; length]`, e.g.
34+
35+
```rust
36+
let arr: [i32; 4] = [10, 20, 30, 40];
37+
```
38+
39+
## Vectors
40+
41+
Vectors are like growable arrays. They are much more common in everyday Rust code. To create a vector with known values, use the `vec![]` macro. Like an array, they must all be the same type.
42+
43+
```rust
44+
fn main() {
45+
let v = vec![1, 2, 3, 4, 5];
46+
println!("Vector: {:?}", v);
47+
}
48+
```
49+
50+
The type of a vector is specified using `Vec<T>` where `T` is shorthand for any type.
51+
52+
An empty vector can be created using `Vec::new()` or `vec![]`. If creating an empty vector, the type must be inferred or made explicit. Vectors also have **methods**. Two handy ones are `.len()` for length, and `.is_empty()` (equivalent of `.len() == 0`)
53+
54+
55+
56+
::: callout-important
57+
## Cannot compile
58+
```rust
59+
fn main() {
60+
let x = Vec::new();
61+
println!("x is empty: {}", x.is_empty());
62+
}
63+
```
64+
:::
65+
66+
This cannot compile because the type of `x` is not known. Rust can infer the type if the vector is used elsewhere where the type is known. To make it compile we must specify the type.
67+
68+
```rust
69+
fn main() {
70+
let x: Vec<f64> = Vec::new();
71+
println!("x is empty: {}", x.is_empty());
72+
}
73+
````
74+
75+
76+
## Exercise
77+
78+
- Create an array of 4 integers and print it.
79+
- Create a vector with 5 numbers and print it using `{:?}`.
80+
- Compare the length of the array and the vector.
81+
- Bonus: create an empty i32 vector.
82+
83+
### Solution
84+
85+
<details>
86+
<summary>View solution</summary>
87+
88+
```rust
89+
fn main() {
90+
let arr = [1, 2, 3, 4];
91+
println!("Array: {:?}", arr);
92+
93+
let v = vec![10, 20, 30, 40, 50];
94+
println!("Vector: {:?}", v);
95+
96+
// Bonus:
97+
let v = vec![42_i32; 0];
98+
}
99+
```
100+
101+
</details>

intro-rust/enums.qmd

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# Enum(eration)s
2+
3+
::: callout-tip
4+
## Objective
5+
6+
Understand what `enum`s are and when you may want to use one.
7+
8+
:::
9+
10+
Oftentimes a variable can only take on a small number of values. This is when an **enumeration** (enum) would be useful.
11+
12+
`enum`s are values that can only take on a select few **variants**. They are defined using the `enum` keyword.
13+
14+
15+
16+
## Enums in R
17+
18+
We use enums _all the time_ in R and almost exclusively as function arguments.
19+
20+
::: aside
21+
The tidyverse design style guide has a great section on enums. See [enumerate options](https://design.tidyverse.org/enumerate-options.html#whats-the-pattern).
22+
:::
23+
24+
```r
25+
args(cor)
26+
```
27+
```r
28+
function(
29+
x, y = NULL, use = "everything",
30+
method = c("pearson", "kendall", "spearman") # 👈🏼 enum!
31+
)
32+
```
33+
34+
::: aside
35+
I've written about this in more detail in my blog post [Enums in R: towards type safe R](https://josiahparry.com/posts/2023-11-10-enums-in-r/)
36+
:::
37+
38+
### Example
39+
40+
For example, it may make sense to create an `enum` that specifies a possible shape.
41+
42+
```rust
43+
enum Shape {
44+
Triangle,
45+
Rectangle,
46+
Pentagon,
47+
Hexagon,
48+
}
49+
```
50+
51+
## Variant-specific behavior
52+
53+
The `Shape` enum can take on only one of those 4 **variants**. An enum is created using the format `EnumName::Variant`.
54+
55+
How do we actually determine behavior based on a variant? This is done using **pattern matching**.
56+
57+
The keyword `match` lets us perform an action based on an enum's variant. It works by listing each variant and the behavior to that happens when that variant is matched.
58+
59+
The pattern match format uses the syntax `Enum::Variant => action`. When using `match` each variant much be _enumerated_:
60+
61+
::: aside
62+
`=>` is often referred to as "fat arrow." But if you say "chompky" I'll also get it.
63+
:::
64+
65+
```rust
66+
match my_shape {
67+
Shape::Triangle => todo!(),
68+
Shape::Rectangle => todo!(),
69+
Shape::Pentagon => todo!(),
70+
Shape::Hexagon => todo!(),
71+
}
72+
```
73+
74+
::: callout-tip
75+
todo!() is a placeholder that can be used to make the compiler happy
76+
:::
77+
78+
79+
### Example
80+
81+
For example we may want to print the number of verticies of a shape:
82+
83+
```rust
84+
let my_shape = Shape::Triangle;
85+
86+
match my_shape {
87+
Shape::Triangle => println!("A triangle has 3 vertices"),
88+
Shape::Rectangle => println!("A rectangle has 4 vertices"),
89+
Shape::Pentagon => println!("A pentagon has 5 vertices"),
90+
Shape::Hexagon => println!("A hexagon has 6 vertices"),
91+
}
92+
```
93+
94+
## Wildcard matching
95+
96+
Sometimes we want to customize behavior on only a subset of variants. We can use a catch all in the match statement `_ =>` use the underscore to signify "everything else".
97+
98+
```rust
99+
match my_shape {
100+
Shape::Hexagon => println!("Hexagons are the bestagons"),
101+
_ => println!("Every other polygon is mid"),
102+
}
103+
```
104+
105+
## Enums can `impl`, too
106+
107+
Enums can have methods too just like a struct using `impl` keyword
108+
109+
```rust
110+
impl Shape {
111+
fn is_bestagon(&self) -> bool {
112+
match self {
113+
Self::Hexagon => true,
114+
_ => false
115+
}
116+
}
117+
}
118+
```
119+
120+
121+
## Exercise 1
122+
123+
- Create an enum called `Measure` with two variants `Euclidean` and `Haversine`
124+
- Create a method called `ndim()` which returns `2` for `Euclidean` and `3` for `Haversine`
125+
126+
<details>
127+
<summary>View solution</summary>
128+
129+
```rust
130+
enum Measure {
131+
Euclidean,
132+
Haversine,
133+
}
134+
135+
impl Measure {
136+
fn ndim(&self) -> i32 {
137+
match self {
138+
Self::Euclidean => 2,
139+
Self::Haversine => 3
140+
}
141+
}
142+
}
143+
```
144+
145+
</details>
146+
147+
## Exercise 2
148+
149+
150+
- Create a new method `distance()` for `Point` struct that returns an `f64`
151+
- Arguments: `&self`, `destination: &Self`, `measure: &Measure`
152+
- When `measure` is `Euclidean` use the `euclidean_distance()` method
153+
- When the variant is `Haversine` use the `haversine_distance()` method
154+
155+
The haversine method is defined as:
156+
157+
<details>
158+
<summary>Code for `haversine_distance()` </summary>
159+
```rust
160+
impl Point {
161+
fn haversine_distance(&self, destination: &Self) -> f64 {
162+
let radius = 6_371_008.7714; // Earth's mean radius in meters
163+
let theta1 = self.y.to_radians(); // Latitude of point 1
164+
let theta2 = destination.y.to_radians(); // Latitude of point 2
165+
let delta_theta = (destination.y - self.y).to_radians(); // Delta Latitude
166+
let delta_lambda = (destination.x - self.x).to_radians(); // Delta Longitude
167+
168+
let a = (delta_theta / 2f64).sin().powi(2)
169+
+ theta1.cos() * theta2.cos() * (delta_lambda / 2f64).sin().powi(2);
170+
171+
2f64 * a.sqrt().asin() * radius
172+
}
173+
}
174+
```
175+
176+
</details>
177+
178+
179+
<details>
180+
<summary>View solution</summary>
181+
```rust
182+
183+
impl Point {
184+
// Demonstrates using pattern matching an enum
185+
fn distance(&self, destination: &Self, measure: &Measure) -> f64 {
186+
match measure {
187+
Measure::Euclidean => self.euclidean_distance(destination),
188+
Measure::Haversine => self.haversine_distance(destination),
189+
}
190+
}
191+
}
192+
```

0 commit comments

Comments
 (0)