Skip to content

Commit

Permalink
Add structs and traits and mandelbrot set project
Browse files Browse the repository at this point in the history
  • Loading branch information
uellenberg committed May 19, 2024
1 parent 813193b commit 8f47b8a
Show file tree
Hide file tree
Showing 12 changed files with 534 additions and 9 deletions.
6 changes: 5 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
"id": "rust_intro",
"name": "Introduction to Rust",
"upload": [
"sections/00_rust_concepts"
"sections/00_rust_concepts",
"sections/01_rust_projects"
],
"templates": "templates",
"lessons": {
"intro_to_rust": {
"next": ["rust_projects"]
},
"rust_projects": {
"next": []
}
},
Expand Down
11 changes: 4 additions & 7 deletions sections/00_rust_concepts/03_rust_syntax/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,10 @@ and using them gives us new ways we can express our code.

## Conclusion

Now that you understand some of Rust's basic syntax, it's time to move onwards.
You'll have access to a series of different projects, which will each walk you through different Rust concepts.
These are real-world projects, and will only interact with parts of Rust that are actually relevant to them.
So, pick the project that relates the most to what you want to use Rust for,
or just go with one that sounds interesting.
If you want to learn more, you can always come back and do another project
(hint: click the text below to view a "map" of every lesson to quickly navigate between them).
Now that you understand some of Rust's basic syntax,
there's just one more concept to cover before you can get to building some projects.
Next, we'll cover how to store data in rust using structs and traits
(which you can think of as the Rust equivalent of object-oriented programming).
Also, take a look at some of the example code in the previous lesson to get a bit more practice with Rust's syntax,
with this information in mind.
Happy coding!
210 changes: 210 additions & 0 deletions sections/00_rust_concepts/04_structs_and_traits/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Structs and Traits

The last thing you need to learn before you can start really diving into Rust is how to properly store data. If you've ever used an object-oriented language before (Java, C#, etc.), then you can think of structs and traits as the Rust version of that.

Structs are essentially ways to create little packets of data. For example, instead of writing functions that take in a user's data as individual parameters, like:

```rust
fn user_function(id: u32, name: String, bio: String) {
// ...
}
```

We can package all this data up into a struct called `User` and use it like this:

```rust
fn user_function(user: User) {
// ...
}
```

This can help make writing code a lot easier. Structs are a lot like classes in other languages, except without functions or methods.

## Structs

In order to create a struct, we need to define it like this:

```rust
struct User {
id: u32,
name: String,
bio: String,
}
```

We start off with `struct`, then the name of the struct afterward. Then, we put all the struct's fields inside curly brackets. Each field is formatted as `name_of_field: DataType`.

Note that, by default, structs are private. That means that they aren't accessible from other files/modules. In order to fix that, you can add a `pub` before `struct`:

```rust
pub struct User {
id: u32,
name: String,
bio: String,
}
```

Struct fields are also private by default. That means that we won't be able to access them from outside files/modules. If we want to make them accessible, we can either create a method to get/set them, or make the fields themselves public:

```rust
pub struct User {
pub id: u32,
pub name: String,
pub bio: String,
}
```

If we have a struct, we can access its fields using a dot syntax, similar to many other languages:

```rust
my_user.id // Get the id field of my_user.
my_user.id = 10; // Set the id field of my_user.
```

And if we want to create an instance of a struct, we can use the following syntax:

```rust
User {
id: 1,
// String literals have a type of &str, but
// the name field wants a String, so we need to
// use into() to convert it.
name: "User Name".into(),
bio: "Bio".into(),
}
```

## Tuple Structs

We can also create a type of struct called a tuple struct. Instead of defining fields, we can define them like a tuple:

```rust
struct UserTuple(u32, String, String);
```

And instead of accessing them by name, we access them by index:

```rust
my_user_tuple.0 // Get the first (u32) field of the user.
```

Like with normal structs, the syntax for creating a new instance of them is very similar to how we define them:

```rust
UserTuple(1, "User Name".into(), "Bio".into())
```

It's worth noting that Rust also supports tuples that aren't structs. So, we can have a function that takes in `(u32, String, String)` directly:

```rust
fn user_tuple_no_struct(user: (u32, String, String)) {
// Print out the first field in the tuple (u32).
println!("{}", user.0);
// ...
}

user_tuple_no_struct((1, "User Name".into(), "Bio".into()));
```

Even though both contain exactly the same data, we aren't able to interchange them. So the following code wouldn't work:

```rust
user_tuple_no_struct(UserTuple(1, "User Name".into(), "Bio".into()));
```

### Zero-Sized Structs

Rust also has structs that contain no data at all. They are only useful in certain cases, so we'll only cover them briefly, but you can define them like this:

```struct
struct NoData;
```

And create them by just using their names:

```rust
NoData
```

## Struct Methods

Even though structs don't have any methods in their definition, we can still add them another way. We'll ue the `impl` block:

```rust
impl User {
pub fn id(&self) -> u32 {
self.id
}
}
```

This creates a function, called `id`, that takes in a reference to a `User` (`&self` means a reference to the thing that the method is implemented on, which in this case is a `User`) and returns a `u32`. If we have an instance of a `User`, called `my_user`, then we can call the function by doing `my_user.id()`. This means the same thing as `User::id(&my_user)` (which is just a different way to call these methods).

If we wanted to modify data on the struct, we could create a function like this:

```rust
pub fn set_bio(&mut self, bio: String) {
self.bio = bio;
}
```

The only difference here is that we take in a `&mut self` instead of a `&self` (because we need to have mutable access to the data if we want to modify it), and we take in a second parameter. To call this, we can use `my_user.set_bio("New Bio".into())`, which is the same thing as `User::set_bio(&mut my_user, "New Bio".into())`.

Finally, we can write a function that doesn't take in an instance of the struct. This is useful if we want to write constructors:

```rust
pub fn new(id: u32, name: String, bio: String) -> User {
User {
// This means the same thing as id: id,
// or setting the id field to the id variable
// above.
// Rust lets us collapse it if both have the same name.
id,
name,
bio,
}
}
```

## Traits

If Rust structs are like classes without methods, then traits are like interfaces. They let us write "blueprints" for methods that need to exist on a class. We can write traits like this:

```rust
trait HasDataID {
fn get_id(&self) -> u32;
}
```

This creates a trait called `HasDataID`. Inside the trait definition is all the methods that need to exist on the trait. If we have something that implements this trait, then it needs to have an `id` method that takes in a reference to itself and returns a `u32`.

To implement this for user, we can write it as:

```rust
impl HasDataID for User {
fn get_id(&self) -> u32 {
self.id
}
}
```

Then, we can write functions like this:

```rust
pub fn print_id(value: impl HasDataID) {
println!("{}", value.get_id());
}
```

This special syntax (`impl HasDataID`) means that the function can take in any piece of data that implements `HasDataID`. So, we could put a `User` in and it would work just fine. Behind the scenes, this is using generics (which we'll get into later), and it actually creates a copy of the function for each different kind of data that gets put into it.

## Conclusion

Now that you know how to work with data in Rust, it's time to move onwards.
You'll have access to a series of different projects, which will each walk you through different Rust concepts.
These are real-world projects, and will only interact with parts of Rust that are actually relevant to them.
So, pick the project that relates the most to what you want to use Rust for,
or just go with one that sounds interesting.
If you want to learn more, you can always come back and do another project
(hint: click the text below to view a "map" of every lesson to quickly navigate between them).
Happy coding!
4 changes: 4 additions & 0 deletions sections/00_rust_concepts/04_structs_and_traits/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"defaultFile": "src/main.rs",
"source": "https://github.com/Cratecode/rust/tree/master/sections/00_rust_concepts/04_structs_and_traits"
}
9 changes: 9 additions & 0 deletions sections/00_rust_concepts/04_structs_and_traits/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "lesson",
"id": "les_rust_structs_traits",
"extends": "basic",
"name": "Rust Structs and Traits",
"unit" : "rust_intro",
"spec": "An example of structs and traits in Rust.",
"class": "tutorial"
}
Loading

0 comments on commit 8f47b8a

Please sign in to comment.