mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 00:43:59 +08:00
commit
a10fc64f51
@ -79,14 +79,13 @@ fn main() {
|
|||||||
<span class="caption">Listing 10-2: Code to find the largest number in *two*
|
<span class="caption">Listing 10-2: Code to find the largest number in *two*
|
||||||
lists of numbers</span>
|
lists of numbers</span>
|
||||||
|
|
||||||
虽然代码能够执行,但是重复的代码是冗余且已于出错的,并且意味着当更新逻辑时需要修改多处地方的代码。
|
虽然代码能够执行,但是重复的代码是冗余且容易出错的,并且意味着当更新逻辑时需要修改多处地方的代码。
|
||||||
|
|
||||||
<!-- Are we safe assuming the reader will be familiar with the term
|
<!-- Are we safe assuming the reader will be familiar with the term
|
||||||
"abstraction" in this context, or do we want to give a brief definition? -->
|
"abstraction" in this context, or do we want to give a brief definition? -->
|
||||||
<!-- Yes, our audience will be familiar with this term. /Carol -->
|
<!-- Yes, our audience will be familiar with this term. /Carol -->
|
||||||
|
|
||||||
为了消除重复,我们可以创建一层抽象,在这个例子中将表现为一个获取任意整型列表作为参数并对其进行处理的函数。这将增加代码的简洁性并让我们将表达和推导寻找列表中最大值的这个概念与使用这个概念的特定位置相互独。
|
为了消除重复,我们可以创建一层抽象,在这个例子中将表现为一个获取任意整型列表作为参数并对其进行处理的函数。这将增加代码的简洁性并让我们将表达和推导寻找列表中最大值的这个概念与使用这个概念的特定位置相互独立。
|
||||||
立。
|
|
||||||
|
|
||||||
在列表 10-3 的程序中将寻找最大值的代码提取到了一个叫做`largest`的函数中。这个程序可以找出两个不同数字列表的最大值,不过列表 10-1 中的代码只存在于一个位置:
|
在列表 10-3 的程序中将寻找最大值的代码提取到了一个叫做`largest`的函数中。这个程序可以找出两个不同数字列表的最大值,不过列表 10-1 中的代码只存在于一个位置:
|
||||||
|
|
||||||
|
@ -59,9 +59,9 @@ names and the types in their signatures</span>
|
|||||||
|
|
||||||
这里`largest_i32`和`largest_char`有着完全相同的函数体,所以能够将这两个函数变成一个来减少重复就太好了。所幸通过引入一个泛型参数就能实现!
|
这里`largest_i32`和`largest_char`有着完全相同的函数体,所以能够将这两个函数变成一个来减少重复就太好了。所幸通过引入一个泛型参数就能实现!
|
||||||
|
|
||||||
为了参数化要定义的函数的签名中的类型,我们需要像给函数的值参数起名那样为这类型参数起一个名字。这里选择了名称`T`。任何标识符抖可以作为类型参数名,选择`T`是因为 Rust 的类型命名规范是骆驼命名法(CamelCase)。另外泛型类型参数的规范也倾向于简短,经常仅仅是一个字母。`T`作为“type”的缩写是大部分 Rust 程序员的首选。
|
为了参数化要定义的函数的签名中的类型,我们需要像给函数的值参数起名那样为这类型参数起一个名字。这里选择了名称`T`。任何标识符都可以作为类型参数名,选择`T`是因为 Rust 的类型命名规范是骆驼命名法(CamelCase)。另外泛型类型参数的规范也倾向于简短,经常仅仅是一个字母。`T`作为“type”的缩写是大部分 Rust 程序员的首选。
|
||||||
|
|
||||||
当需要再函数体中使用一个参数时,必须再函数签名中声明这个参数以便编译器能知道函数体中这个名称的意义。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。类型参数声明位于函数名称与参数列表中间的尖括号中。
|
当需要再函数体中使用一个参数时,必须在函数签名中声明这个参数以便编译器能知道函数体中这个名称的意义。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。类型参数声明位于函数名称与参数列表中间的尖括号中。
|
||||||
|
|
||||||
我们将要定义的泛型版本的`largest`函数的签名看起来像这样:
|
我们将要定义的泛型版本的`largest`函数的签名看起来像这样:
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ values of type `T`</span>
|
|||||||
|
|
||||||
其语法类似于函数定义中的泛型应用。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。
|
其语法类似于函数定义中的泛型应用。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。
|
||||||
|
|
||||||
注意`Point`的定义中是使用了要给泛型类型,我们想要表达的是结构体`Point`对于一些类型`T`是泛型的,而且无论这个泛型是什么,字段`x`和`y`**都是**相同类型的。如果尝试创建一个有不同类型值的`Point`的实例,像列表 10-7 中的代码就不能编译:
|
注意`Point`的定义中是使用了要给泛型类型,我们想要表达的是结构体`Point`对于一些类型`T`是泛型的,而且字段`x`和`y`**都是**相同类型的,无论它具体是何类型。如果尝试创建一个有不同类型值的`Point`的实例,像列表 10-7 中的代码就不能编译:
|
||||||
|
|
||||||
<span class="filename">Filename: src/main.rs</span>
|
<span class="filename">Filename: src/main.rs</span>
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ enum Option<T> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
换句话说`Option<T>`是一个拥有泛型`T`的枚举。它有两个成员:`Some`,它存放了一个类型`T`的值,和不存在任何值的`None`。标准库中只有这一个定义来支持创建任何具体类型的枚举值。“一个可能的值”是一个比具体类型的值更抽象的概念,而 Rust 允许我们不引入重复就能表现抽象的概念。
|
换句话说`Option<T>`是一个拥有泛型`T`的枚举。它有两个成员:`Some`,它存放了一个类型`T`的值,和不存在任何值的`None`。标准库中只有这一个定义来支持创建任何具体类型的枚举值。“一个可能的值”是一个比具体类型的值更抽象的概念,而 Rust 允许我们不引入重复代码就能表现抽象的概念。
|
||||||
|
|
||||||
枚举也可以拥有多个泛型类型。第九章使用过的`Result`枚举定义就是一个这样的例子:
|
枚举也可以拥有多个泛型类型。第九章使用过的`Result`枚举定义就是一个这样的例子:
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ enum Result<T, E> {
|
|||||||
|
|
||||||
`Result`枚举有两个泛型类型,`T`和`E`。`Result`有两个成员:`Ok`,它存放一个类型`T`的值,而`Err`则存放一个类型`E`的值。这个定义使得`Result`枚举能很方便的表达任何可能成功(返回`T`类型的值)也可能失败(返回`E`类型的值)的操作。回忆一下列表 9-2 中打开一个文件的场景,当文件被成功打开`T`被放入了`std::fs::File`类型而当打开文件出现问题时`E`被放入了`std::io::Error`类型。
|
`Result`枚举有两个泛型类型,`T`和`E`。`Result`有两个成员:`Ok`,它存放一个类型`T`的值,而`Err`则存放一个类型`E`的值。这个定义使得`Result`枚举能很方便的表达任何可能成功(返回`T`类型的值)也可能失败(返回`E`类型的值)的操作。回忆一下列表 9-2 中打开一个文件的场景,当文件被成功打开`T`被放入了`std::fs::File`类型而当打开文件出现问题时`E`被放入了`std::io::Error`类型。
|
||||||
|
|
||||||
当发现代码中有多个只有存放的值的类型有所不同的结构体或枚举定义时,你就应该像之前的函数定义中那样引入泛型类型来减少重复。
|
当发现代码中有多个只有存放的值的类型有所不同的结构体或枚举定义时,你就应该像之前的函数定义中那样引入泛型类型来减少重复代码。
|
||||||
|
|
||||||
### 方法定义中的枚举数据类型
|
### 方法定义中的枚举数据类型
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ println!("1 new tweet: {}", tweet.summary());
|
|||||||
|
|
||||||
这会打印出`1 new tweet: horse_ebooks: of course, as you probably already know, people`。
|
这会打印出`1 new tweet: horse_ebooks: of course, as you probably already know, people`。
|
||||||
|
|
||||||
注意因为列表 10-12 中我们在相同的`lib.rs`力定义了`Summarizable` trait 和`NewsArticle`与`Tweet`类型,所以他们是位于同一作用域的。如果这个`lib.rs`是对应`aggregator` crate 的,而别人想要利用我们 crate 的功能外加为其`WeatherForecast`结构体实现`Summarizable` trait,在实现`Summarizable` trait 之前他们首先就需要将其导入其作用域中,如列表 10-13 所示:
|
注意因为列表 10-12 中我们在相同的`lib.rs`里定义了`Summarizable` trait 和`NewsArticle`与`Tweet`类型,所以他们是位于同一作用域的。如果这个`lib.rs`是对应`aggregator` crate 的,而别人想要利用我们 crate 的功能外加为其`WeatherForecast`结构体实现`Summarizable` trait,在实现`Summarizable` trait 之前他们首先就需要将其导入其作用域中,如列表 10-13 所示:
|
||||||
|
|
||||||
<span class="filename">Filename: lib.rs</span>
|
<span class="filename">Filename: lib.rs</span>
|
||||||
|
|
||||||
@ -204,7 +204,7 @@ println!("1 new tweet: {}", tweet.summary());
|
|||||||
|
|
||||||
### trait bounds
|
### trait bounds
|
||||||
|
|
||||||
现在我们定义了 trait 并在类型上实现了这些 trait,也可以对泛型类型参数使用 trait。我们可以限制泛型不再适用于任何类型,编译器会确保其被限制为那么实现了特定 trait 的类型,由此泛型就会拥有我们希望其类型所拥有的功能。这被称为指定泛型的 *trait bounds*。
|
现在我们定义了 trait 并在类型上实现了这些 trait,也可以对泛型类型参数使用 trait。我们可以限制泛型不再适用于任何类型,编译器会确保其被限制为那些实现了特定 trait 的类型,由此泛型就会拥有我们希望其类型所拥有的功能。这被称为指定泛型的 *trait bounds*。
|
||||||
|
|
||||||
例如在列表 10-12 中为`NewsArticle`和`Tweet`类型实现了`Summarizable` trait。我们可以定义一个函数`notify`来调用`summary`方法,它拥有一个泛型类型`T`的参数`item`。为了能够在`item`上调用`summary`而不出现错误,我们可以在`T`上使用 trait bounds 来指定`item`必须是实现了`Summarizable` trait 的类型:
|
例如在列表 10-12 中为`NewsArticle`和`Tweet`类型实现了`Summarizable` trait。我们可以定义一个函数`notify`来调用`summary`方法,它拥有一个泛型类型`T`的参数`item`。为了能够在`item`上调用`summary`而不出现错误,我们可以在`T`上使用 trait bounds 来指定`item`必须是实现了`Summarizable` trait 的类型:
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ pub fn notify<T: Summarizable>(item: T) {
|
|||||||
|
|
||||||
trait bounds 连同泛型类型参数声明一同出现,位于尖括号中的冒号后面。由于`T`上的 trait bounds,我们可以传递任何`NewsArticle`或`Tweet`的实例来调用`notify`函数。列表 10-13 中使用我们`aggregator` crate 的外部代码也可以传递一个`WeatherForecast`的实例来调用`notify`函数,因为`WeatherForecast`同样也实现了`Summarizable`。使用任何其他类型,比如`String`或`i32`,来调用`notify`的代码将不能编译,因为这些类型没有实现`Summarizable`。
|
trait bounds 连同泛型类型参数声明一同出现,位于尖括号中的冒号后面。由于`T`上的 trait bounds,我们可以传递任何`NewsArticle`或`Tweet`的实例来调用`notify`函数。列表 10-13 中使用我们`aggregator` crate 的外部代码也可以传递一个`WeatherForecast`的实例来调用`notify`函数,因为`WeatherForecast`同样也实现了`Summarizable`。使用任何其他类型,比如`String`或`i32`,来调用`notify`的代码将不能编译,因为这些类型没有实现`Summarizable`。
|
||||||
|
|
||||||
可以通过`+`来为泛型指定多个 trait bounds。如果我们需要能够在函数中使用`T`类型的显示格式的同时也能使用`summary`方法,则可以使用 trait bounds `T: Summarizable + Display`。这意味着`T`可以是任何是实现了`Summarizable`和`Display`的类型。
|
可以通过`+`来为泛型指定多个 trait bounds。如果我们需要能够在函数中使用`T`类型的显示格式的同时也能使用`summary`方法,则可以使用 trait bounds `T: Summarizable + Display`。这意味着`T`可以是任何实现了`Summarizable`和`Display`的类型。
|
||||||
|
|
||||||
对于拥有多个泛型类型参数的函数,每一个泛型都可以有其自己的 trait bounds。在函数名和参数列表之间的尖括号中指定很多的 trait bound 信息将是难以阅读的,所以有另外一个指定 trait bounds 的语法,它将其移动到函数签名后的`where`从句中。所以相比这样写:
|
对于拥有多个泛型类型参数的函数,每一个泛型都可以有其自己的 trait bounds。在函数名和参数列表之间的尖括号中指定很多的 trait bound 信息将是难以阅读的,所以有另外一个指定 trait bounds 的语法,它将其移动到函数签名后的`where`从句中。所以相比这样写:
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ line and ends with the first closing curly brace on the 7th line. Do you think
|
|||||||
the text art comments work or should we make an SVG diagram that has nicer
|
the text art comments work or should we make an SVG diagram that has nicer
|
||||||
looking arrows and labels? /Carol -->
|
looking arrows and labels? /Carol -->
|
||||||
|
|
||||||
我们将`r`的声明周期标记为`'a`而将`x`的生命周期标记为`'b`。如你所见,内部的`'b`块要比外部的生命周期`'a`小得多。在编译时,Rust 比较这两个生命周期的大小,并发现`r`拥有声明周期`'a`,不过它引用了一个拥有生命周期`'b`的对象。程序被拒绝编译,因为生命周期`'b`比生命周期`'a`要小:引用者没有比被引用者存在的更久。
|
我们将`r`的声明周期标记为`'a`而将`x`的生命周期标记为`'b`。如你所见,内部的`'b`块要比外部的生命周期`'a`小得多。在编译时,Rust 比较这两个生命周期的大小,并发现`r`拥有声明周期`'a`,不过它引用了一个拥有生命周期`'b`的对象。程序被拒绝编译,因为生命周期`'b`比生命周期`'a`要小:被引用的对象比它的引用者存活的时间更短。
|
||||||
|
|
||||||
让我们看看列表 10-18 中这个并没有产生悬垂引用且可以正常编译的例子:
|
让我们看看列表 10-18 中这个并没有产生悬垂引用且可以正常编译的例子:
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ error[E0106]: missing lifetime specifier
|
|||||||
|
|
||||||
### 函数签名中的生命周期注解
|
### 函数签名中的生命周期注解
|
||||||
|
|
||||||
来看看我们编写的`longest`函数的上下文中的生命周期。就像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的加括号中。这里我们想要告诉 Rust 关于参数中的引用和返回值之间的限制是他们都必须拥有相同的生命周期,就像列表 10-21 中在每个引用中都加上了`'a`那样:
|
来看看我们编写的`longest`函数的上下文中的生命周期。就像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的尖括号中。这里我们想要告诉 Rust 关于参数中的引用和返回值之间的限制是他们都必须拥有相同的生命周期,就像列表 10-21 中在每个引用中都加上了`'a`那样:
|
||||||
|
|
||||||
<span class="filename">Filename: src/main.rs</span>
|
<span class="filename">Filename: src/main.rs</span>
|
||||||
|
|
||||||
@ -400,15 +400,15 @@ fn first_word<'a>(s: &'a str) -> &'a str {
|
|||||||
|
|
||||||
这些规则并不提供完整的推断:如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么。在这种情况,编译器会给出一个错误,这可以通过增加对应引用之间相联系的生命周期注解来解决。
|
这些规则并不提供完整的推断:如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么。在这种情况,编译器会给出一个错误,这可以通过增加对应引用之间相联系的生命周期注解来解决。
|
||||||
|
|
||||||
首先,介绍一些定义定义:函数或方法的参数的生命周期被称为**输入生命周期**(*input lifetimes*),而返回值的生命周期被称为**输出生命周期**(*output lifetimes*)。
|
首先,介绍一些定义:函数或方法的参数的生命周期被称为**输入生命周期**(*input lifetimes*),而返回值的生命周期被称为**输出生命周期**(*output lifetimes*)。
|
||||||
|
|
||||||
现在介绍编译器用于判断引用何时不需要明确生命周期注解的规则。第一条规则适用于输入生命周期,而两条规则则适用于输出生命周期。如果编译器检查完这三条规则并仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。
|
现在介绍编译器用于判断引用何时不需要明确生命周期注解的规则。第一条规则适用于输入生命周期,而后两条规则则适用于输出生命周期。如果编译器检查完这三条规则并仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。
|
||||||
|
|
||||||
1. 每一个是引用的参数都有它自己的生命周期参数。话句话说就是,有一个引用参数的有一个生命周期参数:`fn foo<'a>(x: &'a i32)`,有两个引用参数的函数有两个不同的生命周期参数,`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`,依此类推。
|
1. 每一个是引用的参数都有它自己的生命周期参数。话句话说就是,有一个引用参数的函数有一个生命周期参数:`fn foo<'a>(x: &'a i32)`,有两个引用参数的函数有两个不同的生命周期参数,`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`,依此类推。
|
||||||
|
|
||||||
2. 如果只有一个输入生命周期参数,而且它被赋予所有输出生命周期参数:`fn foo<'a>(x: &'a i32) -> &'a i32`。
|
2. 如果只有一个输入生命周期参数,那么它被赋给所有输出生命周期参数:`fn foo<'a>(x: &'a i32) -> &'a i32`。
|
||||||
|
|
||||||
3. 如果方法有多个输入生命周期参数,不过其中之一是`&self`或`&mut self`,那么`self`的生命周期被赋予所有输出生命周期参数。这使得方法看起来更简洁。
|
3. 如果方法有多个输入生命周期参数,不过其中之一因为方法的缘故是`&self`或`&mut self`,那么`self`的生命周期被赋给所有输出生命周期参数。这使得方法写起来更简洁。
|
||||||
|
|
||||||
假设我们自己就是编译器并来计算列表 10-25 `first_word`函数的签名中的引用的生命周期。开始时签名中的引用并没有关联任何生命周期:
|
假设我们自己就是编译器并来计算列表 10-25 `first_word`函数的签名中的引用的生命周期。开始时签名中的引用并没有关联任何生命周期:
|
||||||
|
|
||||||
@ -456,7 +456,7 @@ parameters need to be declared and used since the lifetime parameters could go
|
|||||||
with the struct's fields or with references passed into or returned from
|
with the struct's fields or with references passed into or returned from
|
||||||
methods. /Carol -->
|
methods. /Carol -->
|
||||||
|
|
||||||
当为带有生命周期的结构体实现方法时,其语法依然类似列表 10-10 中展示的泛型类型参数的语法:包括声明生命周期参数的位置和以及生命周期参数是否与结构体字段或方法的参数与返回值相关联。
|
当为带有生命周期的结构体实现方法时,其语法依然类似列表 10-10 中展示的泛型类型参数的语法:包括声明生命周期参数的位置和生命周期参数是否与结构体字段或方法的参数与返回值相关联。
|
||||||
|
|
||||||
(实现方法时)结构体字段的生命周期必须总是在`impl`关键字之后声明并在结构体名称之后被适用,因为这些生命周期是结构体类型的一部分。
|
(实现方法时)结构体字段的生命周期必须总是在`impl`关键字之后声明并在结构体名称之后被适用,因为这些生命周期是结构体类型的一部分。
|
||||||
|
|
||||||
@ -533,6 +533,6 @@ fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a st
|
|||||||
|
|
||||||
## 总结
|
## 总结
|
||||||
|
|
||||||
这一章介绍了很多的内容!现在你知道了泛型类型参数、trait 和 trait bounds 以及 泛型生命周期类型,你已经准备编写既不重复又能适用于多种场景的代码了。泛型类型参数意味着代码可以适用于不同的类型。trait 和 trait bounds 保证了即使类型是泛型的,这些类型也会拥有所需要的行为。由生命周期注解所指定的引用生命周期之间的关系保证了这些灵活多变的代码不会出现悬垂引用。而所有的这一切,发生在运行时所以不会影响运行时效率!
|
这一章介绍了很多的内容!现在你知道了泛型类型参数、trait 和 trait bounds 以及 泛型生命周期类型,你已经准备编写既不重复又能适用于多种场景的代码了。泛型类型参数意味着代码可以适用于不同的类型。trait 和 trait bounds 保证了即使类型是泛型的,这些类型也会拥有所需要的行为。由生命周期注解所指定的引用生命周期之间的关系保证了这些灵活多变的代码不会出现悬垂引用。而所有的这一切发生在编译时所以不会影响运行时效率!
|
||||||
|
|
||||||
你可能不会相信,这个领域还有更多需要学习的内容:第十七章会讨论 trait 对象,这是另一种使用 trait 的方式。第十九章会涉及到生命周期注解更复杂的场景。第二十章讲解一些高级的类型系统功能。不过接下来,让我们聊聊如何在 Rust 中编写测试,来确保代码的所有功能能像我们希望的那样工作!
|
你可能不会相信,这个领域还有更多需要学习的内容:第十七章会讨论 trait 对象,这是另一种使用 trait 的方式。第十九章会涉及到生命周期注解更复杂的场景。第二十章讲解一些高级的类型系统功能。不过接下来,让我们聊聊如何在 Rust 中编写测试,来确保代码的所有功能能像我们希望的那样工作!
|
@ -4,7 +4,7 @@
|
|||||||
> <br>
|
> <br>
|
||||||
> commit 55b294f20fc846a13a9be623bf322d8b364cee77
|
> commit 55b294f20fc846a13a9be623bf322d8b364cee77
|
||||||
|
|
||||||
正如之前提到的,测试是一个很广泛的学科,而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:**单元测试**(*unit tests*)与**集成测试**(*unit tests*)。单元测试倾向于更小而更专注,在隔离的环境中一次测试一个模块。他们也可以测试私有接口。集成测试对于你的库来说则完全是外部的。他们与其他用户使用相同的方式使用你的代码,他们只针对共有接口而且每个测试都会测试多个模块。
|
正如之前提到的,测试是一个很广泛的学科,而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题:**单元测试**(*unit tests*)与**集成测试**(*integration tests*)。单元测试倾向于更小而更专注,在隔离的环境中一次测试一个模块。他们也可以测试私有接口。集成测试对于你的库来说则完全是外部的。他们与其他用户使用相同的方式使用你的代码,他们只针对公有接口而且每个测试都会测试多个模块。
|
||||||
|
|
||||||
这两类测试对于从独立和整体的角度保证你的库符合期望是非常重要的。
|
这两类测试对于从独立和整体的角度保证你的库符合期望是非常重要的。
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user