trait入门
定义共享行为的接口设计。和
java
以及php
里的抽象类或者接口类似。
目录层级
➜ mypro git:(dev_trait) ✗ tree -L 3
.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│ ├── api
│ │ ├── mod.rs
│ │ └── prods.rs
│ ├── lib
│ │ ├── config.rs
│ │ └── mod.rs
│ ├── main.rs
│ └── models
│ ├── book_model.rs
│ ├── mod.rs
│ └── user_model.rs
定义实体类
定义一个Book
的实体
// 图书实体
#[derive(Debug)]
pub struct Book {
pub id: i32,
pub price: f32,
}
// 所谓的实例化函数
pub fn new_book(id: i32, price: f32) -> Book {
// 表达式
Book {
id: id,
price: price,
}
}
同样的,必须在同级目录里的mod.rs
里进行暴露,让外部可以使用
pub mod book_model;
pub mod user_model; // 这个是上一个demo里的内容
这次我们和上面写在实体里的方法不一样,我们另外新建一个src/api
目录,里面包含一个mod.rs
和一个商品的抽象接口或者抽象类
// 写成接口或抽象类
// crate 比 mod 级别更高
use crate::models::book_model::Book;
pub trait Prods {
fn get_price(&self) -> f32;
}
// 使用 trait 对某一个 struct 定义方法
impl Prods for Book {
fn get_price(&self) -> f32 {
// 让价格加10元
&self.price + 10.0
}
}
这里我们需要使用外部的Book
实体,就不能向main.rs
里直接user
目录层级下的即可,这里需要使用crate
获取上一层,然后再去获取对应的实体。
这里的Prods
就和java
里的interface
一样,只定义一个方法名,而且是以分号结尾,然后另外写方法实现。
main.rs
调用,这里我们在mod.rs
写成这样,方便我们理解
pub mod prods;
pub use prods::*;
直接在mod.rs
里可以调用prods
里的所有pub
的内容;然后在main.rs
里就不用use
很多层。
mod api;
mod models;
use api::Prods;
use models::book_model::*;
fn main() {
let book = new_book(101, 25.0);
println!("{:?}", book.get_price());
}
use api::Prods;
如果我们没有写pub use prods::*;
,那么这里就得写成use api::prods::Prods;
。
调用结果:
cargo build && cargo run
35.0
使用trait创建实体类
我们把实例化的方法放到trait
代码中,不放在模型中
// 写成接口或抽象类
// crate 比 mod 级别更高
use crate::models::book_model::Book;
pub trait Prods {
fn get_price(&self) -> f32;
fn new(id: i32, price: f32) -> Self;
}
// 使用 trait 对某一个 struct 定义方法
impl Prods for Book {
fn get_price(&self) -> f32 {
&self.price + 10.0
}
fn new(id: i32, price: f32) -> Book {
Book { id, price }
}
}
mod api;
mod models;
use api::Prods;
use models::book_model::*;
fn main() {
// 需要指明具体的类型
let book: Book = Prods::new(101, 25.0);
println!("{:?}", book.get_price());
}
需要注意的是,这里let book: Book = Prods::new(101, 25.0);
需要指明特定的类型来接收,否则会报错
在函数中传trait作为参数
mod api;
mod models;
use api::Prods;
use models::book_model::*;
// 需要加上 impl 来说明它是 Prods 的trait
fn show_prod(p: impl Prods) {
println!("商品的价格是: {}", p.get_price())
}
fn main() {
// 需要指明具体的类型
let book: Book = Prods::new(101, 25.0);
println!("{:?}", book.get_price());
show_prod(book)
}
或者使用泛型,不过需要在函数名后面加上<泛型名称: 需要指明的trait>
mod api;
mod models;
use api::Prods;
use models::book_model::*;
fn show_prod<T: Prods>(p: T) {
println!("商品的价格是: {}", p.get_price())
}
fn main() {
// 需要指明具体的类型
let book: Book = Prods::new(101, 25.0);
println!("{:?}", book.get_price());
show_prod(book)
}
在函数中传2个trait参数
案例:计算商品的总价
我们在新增一个手机模型:phone_model.rs
#[derive(Debug)]
pub struct Phone {
pub id: i32,
pub price: f32,
}
不要忘了在mod.rs
里进行引入。
pub mod book_model;
pub mod phone_model;
pub mod user_model;
继续使用Prods
实现Phone
的2个方法
// 写成接口或抽象类
// crate 比 mod 级别更高
use crate::models::book_model::Book;
use crate::models::phone_model::Phone;
pub trait Prods {
fn get_price(&self) -> f32;
fn new(id: i32, price: f32) -> Self;
}
// 使用 trait 对某一个 struct 定义方法
impl Prods for Book {
fn get_price(&self) -> f32 {
&self.price + 10.0
}
fn new(id: i32, price: f32) -> Book {
Book { id, price }
}
}
impl Prods for Phone {
fn new(id: i32, price: f32) -> Phone {
Phone { id, price }
}
fn get_price(&self) -> f32 {
&self.price + 20.0
}
}
实践:
mod api;
mod models;
use api::Prods;
use models::{book_model::*, phone_model::Phone};
fn sum1<T: Prods>(p1: T, p2: T) {
println!("商品总价是: {}", p1.get_price() + p2.get_price())
}
fn sum2<T: Prods, U: Prods>(p1: T, p2: U) {
println!("商品总价是: {}", p1.get_price() + p2.get_price())
}
fn main() {
// 需要指明具体的类型
let book1: Book = Prods::new(101, 25.6);
let book2: Book = Prods::new(102, 30.6);
sum1(book1, book2);
let book3: Book = Prods::new(103, 10.3);
let phone1: Phone = Prods::new(104, 1300.2);
sum2(book3, phone1);
}
➜ mypro git:(dev_trait) ✗ cargo build && cargo run
Compiling mypro v0.1.0 (/Users/wangxin/workspace/rustworkspace/mypro)
Finished dev [unoptimized + debuginfo] target(s) in 0.23s
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/mypro`
商品总价是: 76.2
商品总价是: 1340.5
一个struct对应多个trait
在写一个Stock
的trait
use crate::models::book_model::Book;
pub trait Stock {
fn get_stock(&self) -> i32;
}
impl Stock for Book {
fn get_stock(&self) -> i32 {
123
}
}
多个就需要指定多个的trait
mod api;
mod models;
use api::Prods;
use api::Stock;
use models::book_model::*;
fn show_detail<T: Prods + Stock>(p: T) {
println!("商品的库存是: {}", p.get_stock());
}
fn main() {
// 需要指明具体的类型
let book1: Book = Prods::new(101, 25.6);
show_detail(book1);
}
或者使用where
来简写
fn show_detail<T>(p: T)
where
T: Prods + Stock,
{
println!("商品的库存是: {}", p.get_stock());
}
操作符重载
简单实现了一个2个商品的价格相加的方法
mod api;
mod models;
use api::Prods;
use api::Stock;
use models::book_model::*;
fn sum(p1: Book, p2: Book) {
println!("商品总价是: {}", p1.get_price() + p2.get_price());
}
fn main() {
// 需要指明具体的类型
let book1: Book = Prods::new(101, 25.6);
let book2: Book = Prods::new(110, 30.5);
sum(book1, book2);
}
它这里无法实现
println!("商品总价是: {}", p1 + p2);
所以我们要对这个加号进行重载,加号是在标准库std::ops::Add
方法,它也是一个trait
源码内容:
#[doc(alias = "+")]
pub trait Add<Rhs = Self> {
/// The resulting type after applying the `+` operator.
#[stable(feature = "rust1", since = "1.0.0")]
type Output;
/// Performs the `+` operation.
///
/// # Example
///
/// ```
/// assert_eq!(12 + 1, 13);
/// ```
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
fn add(self, rhs: Rhs) -> Self::Output;
}
我们在prods.rs
里进行重载编写:
impl std::ops::Add<Book> for Book {
// 定义输出类型是什么
type Output = f32;
// 实现加法 A, B B的类型为对应的book类型
fn add(self, rhs: Book) -> f32 {
self.get_price() + rhs.get_price()
}
}
然后我们就可以实现p1+p2
这种形式的加法
mod api;
mod models;
use api::Prods;
use api::Stock;
use models::book_model::*;
fn sum(p1: Book, p2: Book) {
println!("商品总价是: {}", p1 + p2);
}
fn main() {
// 需要指明具体的类型
let book1: Book = Prods::new(101, 25.6);
let book2: Book = Prods::new(110, 30.5);
sum(book1, book2);
}
cargo build && cargo run
商品总价是: 76.1
不要被这里的加法的结果给误导了,因为我们上面有一个get_price
的方法里面是给价格加了10的,所以这里的结果可能就不是直白的两个数值相加的结果。
稍微给sum
函数变通一下就是
fn sum(p1: Book, p2: Book) {
println!(
"p1的价格是: {}, p2的价格是: {}, 商品总价是: {}",
p1.get_price(),
p2.get_price(),
p1 + p2
);
}
cargo build && cargo run
p1的价格是: 35.6, p2的价格是: 40.5, 商品总价是: 76.1