mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-01-23 23:50:25 +08:00
Rework struct example to be simpler
This commit is contained in:
parent
25e3cf2a6f
commit
6e15a5c02c
@ -1,301 +1,271 @@
|
|||||||
# Structs
|
# Structs
|
||||||
|
|
||||||
A `struct`, short for "structure", gives us the ability to name and package
|
A `struct`, short for "structure", is a custom data type that lets us name and
|
||||||
together multiple related values that make up a meaningful group. If you come
|
package together multiple related values that make up a meaningful group. If
|
||||||
from an object-oriented language, a `struct` is like an object's data
|
you come from an object-oriented language, a `struct` is like an object's data
|
||||||
attributes. `struct` and `enum` (that we will talk about in the next chapter)
|
attributes. In the next section of this chapter, we'll talk about how to define
|
||||||
are the building blocks you can use in Rust to create new types in your
|
methods on our structs; methods are how you specify the *behavior* that goes
|
||||||
|
along with a struct's data. The `struct` and `enum` (that we will talk about in
|
||||||
|
Chapter 6) concepts are the building blocks for creating new types in your
|
||||||
program's domain in order to take full advantage of Rust's compile-time type
|
program's domain in order to take full advantage of Rust's compile-time type
|
||||||
checking.
|
checking.
|
||||||
|
|
||||||
Let’s write a program which calculates the distance between two points.
|
One way of thinking about structs is that they are similar to tuples that we
|
||||||
We’ll start off with single variable bindings, and then refactor it to
|
talked about in Chapter 3. Like tuples, the pieces of a struct can be different
|
||||||
use `struct`s instead.
|
types. Unlike tuples, we name each piece of data so that it's clearer what the
|
||||||
|
values mean. Structs are more flexible as a result of these names: we don't
|
||||||
|
have to rely on the order of the data to specify or access the values of an
|
||||||
|
instance.
|
||||||
|
|
||||||
Let’s make a new project with Cargo called `points`. Here’s a short program
|
To define a struct, we enter the keyword `struct` and give the whole struct a
|
||||||
which calculates the distance between two points to put into this project's
|
name. A struct's name should describe what the significance is of these pieces
|
||||||
`src/main.rs`:
|
of data being grouped together. Then, inside curly braces, we define the names
|
||||||
|
of the pieces of data, which we call *fields*, and specify each field's type.
|
||||||
|
For example, a struct to store information about a user account might look like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct User {
|
||||||
|
username: String,
|
||||||
|
email: String,
|
||||||
|
sign_in_count: u64,
|
||||||
|
active: bool,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To use a struct, we create an *instance* of that struct by specifying concrete
|
||||||
|
values for each of the fields. Creating an instance is done by declaring a
|
||||||
|
binding with `let`, stating the name of the struct, then curly braces with
|
||||||
|
`key: value` pairs inside it where the keys are the names of the fields and the
|
||||||
|
values are the data we want to store in those fields. The fields don't have to
|
||||||
|
be specified in the same order in which the struct declared them. In other
|
||||||
|
words, the struct definition is like a general template for the type, and
|
||||||
|
instances fill in that template with particular data to create values of the
|
||||||
|
type. For example, we can declare a particular user like this:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
let user1 = User {
|
||||||
|
email: "someone@example.com",
|
||||||
|
username: "someusername123",
|
||||||
|
active: true,
|
||||||
|
sign_in_count: 1,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
To get a particular value out of a struct, we can use dot notation. If we
|
||||||
|
wanted just this user's email address, we can say `user1.email`.
|
||||||
|
|
||||||
|
## An Example Program
|
||||||
|
|
||||||
|
To understand when we might want to use structs, let’s write a program that
|
||||||
|
calculates the area of a rectangle. We’ll start off with single variable
|
||||||
|
bindings, then refactor our program until we're using `struct`s instead.
|
||||||
|
|
||||||
|
Let’s make a new binary project with Cargo called *rectangles* that will take
|
||||||
|
the length and width of a rectangle specified in pixels and will calculate the
|
||||||
|
area of the rectangle. Here’s a short program that has one way of doing just
|
||||||
|
that to put into our project's `src/main.rs`:
|
||||||
|
|
||||||
Filename: src/main.rs
|
Filename: src/main.rs
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() {
|
fn main() {
|
||||||
let x1 = 0.0;
|
let length1 = 50;
|
||||||
let y1 = 5.0;
|
let width1 = 30;
|
||||||
|
|
||||||
let x2 = 12.0;
|
println!("The area of the rectangle is {}", area(length1, width1));
|
||||||
let y2 = 0.0;
|
|
||||||
|
|
||||||
let answer = distance(x1, y1, x2, y2);
|
|
||||||
|
|
||||||
println!("Point 1: ({}, {})", x1, y1);
|
|
||||||
println!("Point 2: ({}, {})", x2, y2);
|
|
||||||
println!("Distance: {}", answer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
|
fn area(length: u32, width: u32) -> u32 {
|
||||||
let x_squared = f64::powi(x2 - x1, 2);
|
length * width
|
||||||
let y_squared = f64::powi(y2 - y1, 2);
|
|
||||||
|
|
||||||
f64::sqrt(x_squared + y_squared)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's try running this program with `cargo run`:
|
Let's try running this program with `cargo run`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cargo run
|
The area of the rectangle is 1500
|
||||||
Compiling points v0.1.0 (file:///projects/points)
|
|
||||||
Running `target/debug/points`
|
|
||||||
Point 1: (0, 5)
|
|
||||||
Point 2: (12, 0)
|
|
||||||
Distance: 13
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's take a quick look at `distance()` before we move forward. To find the
|
### Refactoring with Tuples
|
||||||
distance between two points, we can use the Pythagorean Theorem. The theorem is
|
|
||||||
named after Pythagoras, who was the first person to mathematically prove this
|
|
||||||
formula. The details aren't that important; just know the theorem says that the
|
|
||||||
formula for the distance between two points is equal to:
|
|
||||||
|
|
||||||
- squaring the distance between the points horizontally (the "x" direction)
|
Our little program works okay; it figures out the area of the rectangle by
|
||||||
- squaring the distance between the points vertically (the "y" direction)
|
calling the `area` function with each dimension. But we can do better. The
|
||||||
- adding those together
|
length and the width are related to each other since together they describe one
|
||||||
- and taking the square root of that.
|
rectangle.
|
||||||
|
|
||||||
So that's what we're implementing here.
|
The issue with this method is evident in the signature of `area`:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
f64::powi(2.0, 3)
|
fn area(length: u32, width: u32) -> u32 {
|
||||||
```
|
```
|
||||||
|
|
||||||
The double colon (`::`) here is a namespace operator. We haven’t talked about
|
The area function is supposed to calculate the area of one rectangle, but our
|
||||||
modules and namespaces in depth yet, but you can think of the `powi()` function
|
function takes two arguments. The arguments are related, but that's not
|
||||||
as being scoped inside of another name. In this case, the name is `f64`, the
|
expressed anywhere in our program itself. It would be more readable and more
|
||||||
same as the type. The `powi()` function takes two arguments: the first is a
|
manageable to group length and width together.
|
||||||
number, and the second is the power that it raises that number to. In this
|
|
||||||
case, the second number is an integer, hence the `i` in its name. Similarly,
|
|
||||||
`sqrt()` is a function under the `f64` module, which takes the square root of
|
|
||||||
its argument.
|
|
||||||
|
|
||||||
## Why `struct`s?
|
We’ve already discussed one way we might do that in Chapter 3: tuples. Here’s a
|
||||||
|
version of our program which uses tuples:
|
||||||
Our little program is okay, but we can do better. The key to seeing this is in
|
|
||||||
the signature of `distance()`:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
|
|
||||||
```
|
|
||||||
|
|
||||||
The distance function is supposed to calculate the distance between two points.
|
|
||||||
But our distance function calculates some distance between four numbers. The
|
|
||||||
first two and last two arguments are related, but that’s not expressed anywhere
|
|
||||||
in our program itself. It would be nicer if we had a way to group `(x1, y1)`
|
|
||||||
and `(x2, y2)` together.
|
|
||||||
|
|
||||||
We’ve already discussed one way to do that: tuples. Here’s a version of our
|
|
||||||
program which uses tuples:
|
|
||||||
|
|
||||||
Filename: src/main.rs
|
Filename: src/main.rs
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() {
|
fn main() {
|
||||||
let p1 = (0.0, 5.0);
|
let rect1 = (50, 30);
|
||||||
|
|
||||||
let p2 = (12.0, 0.0);
|
println!("The area of the rectangle is {}", area(rect1));
|
||||||
|
|
||||||
let answer = distance(p1, p2);
|
|
||||||
|
|
||||||
println!("Point 1: {:?}", p1);
|
|
||||||
println!("Point 2: {:?}", p2);
|
|
||||||
println!("Distance: {}", answer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn distance(p1: (f64, f64), p2: (f64, f64)) -> f64 {
|
fn area(dimensions: (u32, u32)) -> u32 {
|
||||||
let x_squared = f64::powi(p2.0 - p1.0, 2);
|
dimensions.0 * dimensions.1
|
||||||
let y_squared = f64::powi(p2.1 - p1.1, 2);
|
|
||||||
|
|
||||||
f64::sqrt(x_squared + y_squared)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a little better, for sure. Tuples let us add a little bit of structure.
|
In one way, this is a little better. Tuples let us add a little bit of
|
||||||
We’re now passing two arguments, so that’s more clear. But it’s also worse:
|
structure, and we’re now passing just one argument. But in another way this
|
||||||
tuples don’t give names to their elements, so our calculation has gotten more
|
method less clear: tuples don’t give names to their elements, so our
|
||||||
confusing:
|
calculation has gotten more confusing because we have to use the tuple's index:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
p2.0 - p1.0
|
dimensions.0 * dimensions.1
|
||||||
p2.1 - p1.1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
When writing this example, your authors almost got it wrong themselves! Distance
|
It doesn't matter if we mix up length and width for the area calculation, but
|
||||||
is all about `x` and `y` points, but our code is talking about `0` and `1`.
|
if we were to draw the rectangle on the screen it would matter! We would have
|
||||||
This isn’t great.
|
to remember that `length` was the tuple index `0` and `width` was the tuple
|
||||||
|
index `1`. If someone else was to work on this code, they would have to figure
|
||||||
|
this out and remember it as well. It would be easy to forget or mix these
|
||||||
|
values up and cause errors, since we haven't conveyed the meaning of our data
|
||||||
|
in our code.
|
||||||
|
|
||||||
Enter `struct`s. We can transform our tuples into something with a name for the
|
### Refactoring with Structs: Adding More Meaning
|
||||||
whole as well as names for the parts:
|
|
||||||
|
|
||||||
```rust,ignore
|
Here is where we bring in `struct`s. We can transform our tuple into a data
|
||||||
let p1 = (0.0, 5.0);
|
type with a name for the whole as well as names for the parts:
|
||||||
|
|
||||||
struct Point {
|
|
||||||
x: f64,
|
|
||||||
y: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
let p1 = Point { x: 0.0, y: 5.0 };
|
|
||||||
```
|
|
||||||
|
|
||||||
Here we've defined a `struct` and given it the name `Point`. The parts inside
|
|
||||||
`{}` are defining the _fields_ of the struct. We can have as many or as few of
|
|
||||||
them as we'd like, and we give them a name and specify their type. Here we have
|
|
||||||
two fields named `x` and `y`, and they both hold `f64`s.
|
|
||||||
|
|
||||||
We can access the field of a struct in the same way we access an element of
|
|
||||||
a tuple, except we use its name:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
let p1 = (0.0, 5.0);
|
|
||||||
let x = p1.0;
|
|
||||||
|
|
||||||
struct Point {
|
|
||||||
x: f64,
|
|
||||||
y: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
let p1 = Point { x: 0.0, y: 5.0 };
|
|
||||||
let x = p1.x;
|
|
||||||
```
|
|
||||||
|
|
||||||
Let’s convert our program to use our `Point` `struct`. Here’s what it looks
|
|
||||||
like now:
|
|
||||||
|
|
||||||
Filename: src/main.rs
|
Filename: src/main.rs
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Debug,Copy,Clone)]
|
struct Rectangle {
|
||||||
struct Point {
|
length: u32,
|
||||||
x: f64,
|
width: u32,
|
||||||
y: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let p1 = Point { x: 0.0, y: 5.0};
|
let rect1 = Rectangle { length: 50, width: 30 };
|
||||||
|
|
||||||
let p2 = Point { x: 12.0, y: 0.0};
|
println!("The area of the rectangle is {}", area(&rect1));
|
||||||
|
|
||||||
let answer = distance(p1, p2);
|
|
||||||
|
|
||||||
println!("Point 1: {:?}", p1);
|
|
||||||
println!("Point 2: {:?}", p2);
|
|
||||||
println!("Distance: {}", answer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn distance(p1: Point, p2: Point) -> f64 {
|
fn area(rectangle: &Rectangle) -> u32 {
|
||||||
let x_squared = f64::powi(p2.x - p1.x, 2);
|
rectangle.length * rectangle.width
|
||||||
let y_squared = f64::powi(p2.y - p1.y, 2);
|
|
||||||
|
|
||||||
f64::sqrt(x_squared + y_squared)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Our function signature for `distance()` now says exactly what we mean: it
|
Here we've defined a `struct` and given it the name `Rectangle`. Inside the
|
||||||
calculates the distance between two `Point`s. And rather than `0` and `1`,
|
`{}` we defined the fields to be `length` and `width`, both of which have type
|
||||||
we’ve got back our `x` and `y`. This is a win for clarity.
|
`u32`. Then in `main`, we create a particular instance of a `Rectangle` that
|
||||||
|
has a length of 50 and a width of 30.
|
||||||
|
|
||||||
## Derived Traits
|
Our `area` function now takes one argument that we've named `rectangle` whose
|
||||||
|
type is an immutable borrow of a struct `Rectangle` instance. As we covered in
|
||||||
|
Chapter 4, we want to borrow the struct rather than take ownership of it so
|
||||||
|
that `main` keeps its ownership and can continue using `rect1`, so that's why
|
||||||
|
we have the `&` in the function signature and at the call site.
|
||||||
|
|
||||||
There’s one other thing that’s a bit strange here, this stuff above the
|
The `area` function accesses the `length` and `width` fields of the `Rectangle`
|
||||||
`struct` declaration:
|
instance it got as an argument. Our function signature for `area` now says
|
||||||
|
exactly what we mean: calculate the area of a `Rectangle`, using its `length`
|
||||||
|
and `width` fields. This conveys that the length and width are related to each
|
||||||
|
other, and gives descriptive names to the values rather than using the index
|
||||||
|
values of `0` and `1`. This is a win for clarity.
|
||||||
|
|
||||||
```rust,ignore
|
### Adding Useful Functionality with Derived Traits
|
||||||
#[derive(Debug,Copy,Clone)]
|
|
||||||
struct Point {
|
|
||||||
```
|
|
||||||
|
|
||||||
This is an annotation that tells the compiler our struct should get some
|
It'd be nice to be able to print out an instance of our `Rectangle` while we're
|
||||||
default behavior for the `Debug`, `Copy`, and `Clone` traits. We talked about
|
debugging our program and be able to see the values for all its fields. Let's
|
||||||
marking that types can be `Copy` and `Clone`-able in Chapter XX when we
|
try using the `println!` macro as we have been and see what happens:
|
||||||
discussed ownership. `Debug` is the trait that enables us to print out our
|
|
||||||
struct so that we can see its value while we are debugging our code.
|
|
||||||
|
|
||||||
So far, we’ve been printing values using `{}` in a `println!` macro. If we try
|
|
||||||
that with a struct, however, by default, we'll get an error. Say we have the
|
|
||||||
following program:
|
|
||||||
|
|
||||||
Filename: src/main.rs
|
Filename: src/main.rs
|
||||||
|
|
||||||
```rust,ignore
|
```rust
|
||||||
struct Point {
|
struct Rectangle {
|
||||||
x: f64,
|
length: u32,
|
||||||
y: f64,
|
width: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let p1 = Point { x: 0.0, y: 5.0};
|
let rect1 = Rectangle { length: 50, width: 30 };
|
||||||
println!("Point 1: {}", p1);
|
|
||||||
|
println!("The rectangle is {}", rect1);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This code tries to print the `p1` point directly, which may seem innocuous. But
|
If we run this, we get an error with the core message ``the trait bound
|
||||||
running it produces the following output:
|
`Rectangle: std::fmt::Display`` is not satisfied`. The `println!` macro can do
|
||||||
|
many kinds of formatting, and by default, `{}` tells `println!` to use a kind
|
||||||
|
of formatting known as `Display`: output intended for direct end-user
|
||||||
|
consumption. The primitive types we’ve seen so far implement `Display` by
|
||||||
|
default, as there’s only one way you’d want to show a `1` or any other
|
||||||
|
primitive type to a user. But with structs, the way `println!` should format
|
||||||
|
the output is less clear as there are more display options: Do you want commas
|
||||||
|
or not? Do you want to print the struct `{}`s? Should all the fields be shown?
|
||||||
|
Because of this ambiguity, Rust doesn't try to guess what we want and structs
|
||||||
|
do not have a provided implementation of `Display`.
|
||||||
|
|
||||||
```bash
|
If we keep reading the error messages, though, we'll find ``note: `Rectangle`
|
||||||
$ cargo run
|
cannot be formatted with the default formatter; try using `:?` instead if you
|
||||||
Compiling points v0.1.0 (file:///projects/points)
|
are using a format string``. Let's try it! The `println!` will now look like
|
||||||
error: the trait bound `Point: std::fmt::Display` is not satisfied [--explain E0277]
|
`println!("The rectangle is {:?}", rect1);`. Putting the specifier `:?` inside
|
||||||
--> src/main.rs:8:29
|
the `{}` tells `println!` we want to use an output format called `Debug`.
|
||||||
8 |> println!("Point 1: {}", p1);
|
`Debug` is a trait that enables us to print out our struct in a way that is
|
||||||
|> ^^
|
useful for developers so that we can see its value while we are debugging our
|
||||||
<std macros>:2:27: 2:58: note: in this expansion of format_args!
|
code.
|
||||||
<std macros>:3:1: 3:54: note: in this expansion of print! (defined in <std macros>)
|
|
||||||
src/main.rs:8:5: 8:33: note: in this expansion of println! (defined in <std macros>)
|
|
||||||
note: `Point` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
|
|
||||||
note: required by `std::fmt::Display::fmt`
|
|
||||||
```
|
|
||||||
|
|
||||||
Whew! The core of the error is this part: *the trait bound `Point:
|
Let's try running with this change and... drat. We still get an error: ``the
|
||||||
std::fmt::Display` is not satisfied*. `println!` can do many kinds of
|
trait bound `Rectangle: std::fmt::Debug` is not satisfied``. Again, though, the
|
||||||
formatting. By default, `{}` implements a kind of formatting known as
|
compliler has given us a helpful note! ``note: `Rectangle` cannot be formatted
|
||||||
`Display`: output intended for direct end-user consumption. The primitive types
|
using `:?`; if it is defined in your crate, add `#[derive(Debug)]` or manually
|
||||||
we’ve seen implement `Display`, as there’s only one way you’d show a `1` to a
|
implement it``.
|
||||||
user. But with structs, the output is less clear. Do you want commas or not?
|
|
||||||
What about the `{}`s? Should all the fields be shown?
|
|
||||||
|
|
||||||
More complex types in the standard library and that are defined by the
|
Rust *does* include functionality to print out debugging information, but we
|
||||||
programmer do not automatically implement `Display` formatting. Standard
|
have to explicitly opt-in to having that functionality be available for our
|
||||||
library types implement `Debug` formatting, which is intended for the
|
struct. To do that, we add the annotation `#[derive(Debug)]` just before our
|
||||||
programmer to see. The `#[derive(Debug)]` annotation lets us use a default
|
struct definition, so now our program looks like this:
|
||||||
implementation of `Debug` formatting to easily get this ability for types we've
|
|
||||||
defined. To ask `println!` to use `Debug` formatting with our `Point`, we add
|
|
||||||
the annotation to derive the trait and include `:?` in the print string, like
|
|
||||||
this:
|
|
||||||
|
|
||||||
Filename: src/main.rs
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Point {
|
struct Rectangle {
|
||||||
x: f64,
|
length: u32,
|
||||||
y: f64,
|
width: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let p1 = Point { x: 0.0, y: 5.0};
|
let rect1 = Rectangle { length: 50, width: 30 };
|
||||||
println!("Point 1: {:?}", p1);
|
|
||||||
|
println!("The rectangle is {:?}", rect1);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you run this, it should print the values of each field in the `Point` struct
|
*Now* if we run this program, we won't get any errors and we'll see the
|
||||||
as desired:
|
following output:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cargo run
|
The rectangle is Rectangle { length: 50, width: 30 }
|
||||||
Compiling points v0.1.0 (file:///projects/points)
|
|
||||||
Running `target/debug/points`
|
|
||||||
Point 1: Point { x: 0, y: 5 }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You’ll see this repeated later with other types. We’ll cover traits fully in
|
Neat! It's not the prettiest output, but it shows the values of all the fields
|
||||||
Chapter XX.
|
for this instance, which would definitely help during debugging.
|
||||||
|
|
||||||
|
There are a number of traits Rust has provided for us to use with the `derive`
|
||||||
|
annotation that can add useful behavior to our custom types. Those traits and
|
||||||
|
their behaviors are listed in Appendix XX. We'll be covering how to implement
|
||||||
|
these traits with different behavior, as well as creating your own traits, in
|
||||||
|
Chapter 10.
|
||||||
|
|
||||||
|
Our `area` function is pretty specific-- it only computes the area of
|
||||||
|
rectangles. It would be nice to tie this behavior together more closely with our
|
||||||
|
`Rectangle` struct, since it's behavior that our `Rectangle` type has
|
||||||
|
specifically. Let's now look at how we can continue to refactor this code by
|
||||||
|
turning the `area` function into an `area` *method* defined on our `Rectangle`
|
||||||
|
type.
|
||||||
|
Loading…
Reference in New Issue
Block a user