トレイトとは
型が持つべき振る舞いを定義する仕組みです。他言語のインターフェースに相当します。
// トレイトの定義
trait Summary {
fn summarize(&self) -> String;
}
// 構造体
struct Article {
title: String,
author: String,
content: String,
}
// トレイトの実装
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
let article = Article {
title: String::from("Rust入門"),
author: String::from("太郎"),
content: String::from("Rustは..."),
};
println!("{}", article.summarize());
デフォルト実装
trait Summary {
fn summarize(&self) -> String {
String::from("(続きを読む...)")
}
fn summarize_author(&self) -> String;
// デフォルト実装から他のメソッドを呼び出し
fn full_summary(&self) -> String {
format!("{}: {}", self.summarize_author(), self.summarize())
}
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// summarize はデフォルト実装を使用
}
トレイト境界
引数としてのトレイト
// impl Trait 構文
fn notify(item: &impl Summary) {
println!("速報: {}", item.summarize());
}
// トレイト境界構文
fn notify<T: Summary>(item: &T) {
println!("速報: {}", item.summarize());
}
// 複数のトレイト
fn notify(item: &(impl Summary + Display)) {
// ...
}
fn notify<T: Summary + Display>(item: &T) {
// ...
}
where句
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// ...
}
戻り値としてのトレイト
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("user"),
content: String::from("Hello!"),
}
}
ジェネリクスとトレイト
use std::cmp::PartialOrd;
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("最大: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("最大: {}", largest(&chars));
}
標準トレイト
Display と Debug
use std::fmt;
struct Point {
x: f64,
y: f64,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl fmt::Debug for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Point")
.field("x", &self.x)
.field("y", &self.y)
.finish()
}
}
let p = Point { x: 1.0, y: 2.0 };
println!("{}", p); // Display: (1, 2)
println!("{:?}", p); // Debug: Point { x: 1.0, y: 2.0 }
Clone と Copy
#[derive(Clone)]
struct Person {
name: String,
age: u32,
}
let p1 = Person { name: String::from("太郎"), age: 25 };
let p2 = p1.clone(); // 明示的なクローン
// Copy トレイト(スタック上のコピー)
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // コピー(p1は引き続き使用可能)
PartialEq と Eq
#[derive(PartialEq)]
struct Point {
x: f64,
y: f64,
}
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = Point { x: 1.0, y: 2.0 };
println!("{}", p1 == p2); // true
PartialOrd と Ord
#[derive(PartialEq, PartialOrd)]
struct Score {
value: i32,
}
let a = Score { value: 85 };
let b = Score { value: 90 };
println!("{}", a < b); // true
From と Into
struct Wrapper(String);
impl From<String> for Wrapper {
fn from(s: String) -> Self {
Wrapper(s)
}
}
impl From<&str> for Wrapper {
fn from(s: &str) -> Self {
Wrapper(s.to_string())
}
}
let w1: Wrapper = String::from("hello").into();
let w2 = Wrapper::from("world");
トレイトオブジェクト
trait Draw {
fn draw(&self);
}
struct Circle {
radius: f64,
}
impl Draw for Circle {
fn draw(&self) {
println!("円を描画(半径: {})", self.radius);
}
}
struct Rectangle {
width: f64,
height: f64,
}
impl Draw for Rectangle {
fn draw(&self) {
println!("長方形を描画({}x{})", self.width, self.height);
}
}
// トレイトオブジェクトを使用
fn draw_all(shapes: &[&dyn Draw]) {
for shape in shapes {
shape.draw();
}
}
fn main() {
let circle = Circle { radius: 5.0 };
let rect = Rectangle { width: 10.0, height: 5.0 };
let shapes: Vec<&dyn Draw> = vec![&circle, &rect];
draw_all(&shapes);
}
関連型
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {
count: u32,
max: u32,
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
実践例:シリアライズ可能なトレイト
trait Serialize {
fn serialize(&self) -> String;
}
trait Deserialize: Sized {
fn deserialize(data: &str) -> Option<Self>;
}
#[derive(Debug)]
struct User {
id: u32,
name: String,
}
impl Serialize for User {
fn serialize(&self) -> String {
format!("{}:{}", self.id, self.name)
}
}
impl Deserialize for User {
fn deserialize(data: &str) -> Option<Self> {
let parts: Vec<&str> = data.split(':').collect();
if parts.len() != 2 {
return None;
}
let id = parts[0].parse().ok()?;
let name = parts[1].to_string();
Some(User { id, name })
}
}
fn main() {
let user = User { id: 1, name: String::from("太郎") };
let serialized = user.serialize();
println!("シリアライズ: {}", serialized);
if let Some(user2) = User::deserialize(&serialized) {
println!("デシリアライズ: {:?}", user2);
}
}
演算子オーバーロード
use std::ops::Add;
#[derive(Debug, Clone, Copy)]
struct Point {
x: f64,
y: f64,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = Point { x: 3.0, y: 4.0 };
let p3 = p1 + p2;
println!("{:?}", p3); // Point { x: 4.0, y: 6.0 }
まとめ
traitで振る舞いを定義impl Trait for Typeで実装- デフォルト実装でボイラープレートを削減
- トレイト境界でジェネリクスを制約
impl Traitで戻り値の型を抽象化dyn Traitでトレイトオブジェクト- 関連型でトレイト内で型を定義
- 標準トレイトを実装して機能を追加
これでRust基礎は完了です。次は所有権編でRust特有のメモリ管理を深く学びましょう。