Start traits

pull/1/head
Dhghomon 4 years ago committed by GitHub
parent a7c026d38f
commit 22f7881799
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2194,7 +2194,7 @@ enum Result<T, E> {
}
```
So Result has a value inside of Ok, and a value inside of Err. That is because errors usually have information, because there are many types of errors.
So Result has a value inside of Ok, and a value inside of Err. That is because errors usually have information inside them.
```Result<T, E>``` means you need to think of what you want to return for Ok, and what you want to return for Err. Actually, you can decide anything. Even this is okay:
@ -2255,3 +2255,142 @@ thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "There w
This information helps you fix your code. src\main.rs:30:20 means "inside main.rs in directory src, on line 30 and column 20". So you can go there to look at your code and fix the problem.
You can also create your own error types. Result functions in the standard library usually have their own error types. For example:
```
pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>
```
This function take a vector of bytes (u8) and tries to make a ```String```. So the success case for the Result is a ```String``` and the error case is ```FromUtf8Error```. You can give your error type any name you want. We will create our own error types later, because first we need to learn other things.
# Traits
We have seen traits before: Debug, Copy, Clone are all traits. To give a type a trait, you have to implement it. Because Debug and the others are so common, it's easy to do:
```rust
#[derive(Debug)]
struct MyStruct {
number: usize,
}
```
But other traits are more difficult, so you need to implement them manually with ```impl```. For example, std::ops::Add is used to add two things. But you can add in many ways.
```rust
struct ThingsToAdd {
first_thing: u32,
second_thing: f32,
}
```
We can add ```first_thing``` and ```second_thing```, but we need to give more information. Maybe we want an f32, so something like this:
```rust
let result = self.second_thing + self.first_thing as f32
```
But maybe we want an integer, so like this:
```rust
let result = self.second_thing as u32 + self.first_thing
```
Or maybe we want to just put ```self.first_thing``` next to ```self.second_thing``` and say that this is how we want to add. So if we add 55 to 33.4, we want to see 5533, not 88.
So first let's look at how to make a trait. To make a trait, write ```trait``` and then create some functions.
```rust
struct Animal { // A simple struct - an Animal only has a name
name: String,
}
trait Dog { // The dog trait gives some functionality
fn bark(&self) { // It can bark
println!("Woof woof!");
}
fn run(&self) { // and it can run
println!("The dog is running!");
}
}
impl Dog for Animal {} // Now Animal has the trait Dog
fn main() {
let rover = Animal {
name: "Rover".to_string(),
};
rover.bark(); // Now Animal can use bark()
rover.run(); // and it can use run()
}
```
This is okay, but we don't want to print "The dog is running". We can change the method .run(), but we have to follow the signature. The signature says:
```rust
fn run(&self) {
println!("The dog is running!");
}
```
The signature says "fn run() takes &self, and returns nothing". So you can't do this:
```rust
fn run(&self) -> i32 {
5
}
```
Rust will say:
```
= note: expected fn pointer `fn(&Animal)`
found fn pointer `fn(&Animal) -> i32`
```
But we can do this:
```rust
impl Dog for Animal {
fn run(&self) {
println!("{} is running!", self.name);
}
}
```
Now it prints ```Rover is running!```. This is okay because we are returning (), or nothing, which is what the trait says.
Actually, a trait doesn't need to write out the whole function. Now we change bark() and run() to just say ```fn bark(&self)``` and ```fn run(&self);```. This is not a full function, so the user must write it.
```rust
struct Animal {
name: String,
}
trait Dog {
fn bark(&self); // bark() says it needs a &self and returns nothing
fn run(&self); // run() says it needs a &self and returns nothing.
// So now we have to write them ourselves.
}
impl Dog for Animal {
fn bark(&self) {
println!("{}, stop barking!!", self.name);
}
fn run(&self) {
println!("{} is running!", self.name);
}
}
fn main() {
let rover = Animal {
name: "Rover".to_string(),
};
rover.bark();
rover.run();
}
```
So when you create a trait, you must think: "Which functions should I write, and which functions should the user write?" If you think the user will use the function the same way every time, then write out the function. If you think the user will use it differently, then just write the function signature.

Loading…
Cancel
Save