[Rust 入門] #07 トレイトとジェネリクス — Java の interface と並べて動かして確かめる

[Rust 入門] #07 トレイトとジェネリクス — Java の interface と並べて動かして確かめる

はじめに

第3回から第6回まで、説明を後回しにした書き方が、いくつか残っています。

出てきた回書き方今回の節
第3回does not implement the Copy trait、impl Drop for Noisy標準のトレイト
第4回&[T]・Vec<T> の Tジェネリクス
第5回#[derive(Debug)]、#[derive(Clone, Copy)]標準のトレイト
第6回Option<T> の T、? の裏の Fromジェネリクス、標準のトレイト

今回は、トレイト(型に共通の動作を約束させる仕組み)と、ジェネリクス(どんな型にも使える関数や型を書く仕組み)です。

トレイト(trait)

「この動作ができる型」を、名前を付けて決めるのが、トレイトです。

trait Describe {
    fn describe(&self) -> String;

    fn describe_twice(&self) -> String {
        format!("{0} / {0}", self.describe())
    }
}

struct Item {
    name: String,
    qty: u32,
}

enum Shape {
    Circle(f64),
    Point,
}

impl Describe for Item {
    fn describe(&self) -> String {
        format!("{} × {}", self.name, self.qty)
    }
}

impl Describe for Shape {
    fn describe(&self) -> String {
        match self {
            Shape::Circle(r) => format!("半径 {r} の円"),
            Shape::Point => String::from("点"),
        }
    }

    fn describe_twice(&self) -> String {
        String::from("(Shape は独自の describe_twice)")
    }
}

fn show<T: Describe>(x: &T) {
    println!("{}", x.describe());
}

fn show_impl(x: &impl Describe) {
    println!("[impl] {}", x.describe());
}

fn main() {
    let item = Item {
        name: String::from("りんご"),
        qty: 3,
    };
    let shape = Shape::Circle(1.5);
    show(&item);
    show(&shape);
    show_impl(&item);
    println!("{}", item.describe_twice());
    println!("{}", shape.describe_twice());
    println!("{}", Shape::Point.describe());
}
りんご × 3
半径 1.5 の円
[impl] りんご × 3
りんご × 3 / りんご × 3
(Shape は独自の describe_twice)
点
部分意味
trait Describe { fn describe(&self) -> String; }トレイトの定義。「describe を持つ」という約束。中身は書かず、形だけ
fn describe_twice(&self) -> String { ... }中身つきで書くと、デフォルトになる。実装側が書かなければ、これが使われる
impl Describe for Item { ... }Item が、その約束を果たす宣言。中身を書く
fn show<T: Describe>(x: &T)「Describe を実装した型 T」なら、何でも受ける関数。T: Describe をトレイト境界と言う
fn show_impl(x: &impl Describe)同じ意味の、短い書き方
format!("{0} / {0}", ...){0} は、1つ目の引数。同じ値を2回使える

Item は、デフォルトの describe_twice を使い、Shape は、自分で書き直しています。

実装を書き忘れたとき

約束の関数を、1つも書かないと、エラーです。

impl Describe for Item {}
error[E0046]: not all trait items implemented, missing: `describe`
 --> t2.rs:9:1
  |
2 |     fn describe(&self) -> String;
  |     ----------------------------- `describe` from trait
...
9 | impl Describe for Item {}
  | ^^^^^^^^^^^^^^^^^^^^^^ missing `describe` in implementation

実装していない型には、使えない

Describe を実装していない型を、show に渡すと、エラーです。

struct Other;

show(&Item { name: String::from("りんご") });
show(&Other);
error[E0277]: the trait bound `Other: Describe` is not satisfied
  --> t3.rs:23:10
   |
23 |     show(&Other);
   |     ---- ^^^^^^ unsatisfied trait bound
   |     |
   |     required by a bound introduced by this call
   |
help: the trait `Describe` is implemented for `Item`
  --> t3.rs:11:1
   |
11 | impl Describe for Item {
   | ^^^^^^^^^^^^^^^^^^^^^^
note: required by a bound in `show`
  --> t3.rs:17:12
   |
17 | fn show<T: Describe>(x: &T) {
   |            ^^^^^^^^ required by this bound in `show`

メソッドを直接呼んでも、同じです。

let o = Other;
println!("{}", o.describe());
error[E0599]: no method named `describe` found for struct `Other` in the current scope
 --> t3b.rs:9:22
  |
9 |     println!("{}", o.describe());
  |                      ^^^^^^^^ method not found in `Other`
  |
  = help: items from traits can only be used if the trait is implemented and in scope

第3回の E0382 に出ていた does not implement the Copy trait の trait は、この仕組みのことです。

既存の型にも、実装できる

自分で作った型だけでなく、i32 や String にも、自分のトレイトを実装できました。

impl Describe for i32 {
    fn describe(&self) -> String {
        format!("整数 {self}")
    }
}

impl Describe for String {
    fn describe(&self) -> String {
        format!("文字列 「{self}」")
    }
}

fn main() {
    println!("{}", 5.describe());
    println!("{}", String::from("abc").describe());
}
整数 5
文字列 「abc」

ただし、標準のトレイトを、標準の型に実装することは、できません。

impl fmt::Display for Vec<i32> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "vec")
    }
}
error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
 --> t5.rs:3:1
  |
3 | impl fmt::Display for Vec<i32> {
  | ^^^^^^^^^^^^^^^^^^^^^^--------
  |                       |
  |                       `Vec` is not defined in the current crate
  |
  = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
  = note: define and implement a trait or new type instead

Display も Vec も、自分のプロジェクト(エラーメッセージでは crate)の外にあるので、止まります。自分のトレイトなら、外の型(i32)にも実装できました。自分の型なら、標準のトレイト(Display)も実装できました(あとの Item の例)。

型の違うものを、まとめて扱う: dyn

違う型の値を、同じ配列に入れて、同じように呼ぶには、dyn を使います。

let item = Item;
let shape = Shape;
let list: [&dyn Describe; 2] = [&item, &shape];
for d in list {
    println!("{}", d.describe());
}
品物
図形

(この例の Item と Shape は、中身のない型で、describe は「品物」「図形」を返します)

&dyn Describe は、「Describe を実装した、何かへの参照」です。Item と Shape という、別の型が、1つの配列に入りました。

Java の interface と並べる

Java で、同じ形を書きます。

interface Describe {
    String describe();
    default String describeTwice() {
        return describe() + " / " + describe();
    }
}

static class Item implements Describe {
    String name;
    int qty;
    Item(String name, int qty) { this.name = name; this.qty = qty; }
    public String describe() { return name + " x " + qty; }
}

static <T extends Describe> void show(T x) {
    System.out.println(x.describe());
}
apple x 3
apple x 3 / apple x 3
apple x 3

(show(item)、item.describeTwice()、Describe d = item; d.describe() の順に出力)

Java(21)Rust
約束の書き方interface Describe { ... }trait Describe { ... }
実装する場所クラスの定義に implements Describe と書く型の定義とは別に、impl Describe for 型 と書く
デフォルトの中身default メソッドトレイトの中に、そのまま書く
「この約束を果たす型」だけ受ける<T extends Describe><T: Describe>
実装が足りないときコンパイルエラーE0046(上の例)

ジェネリクス

型を決めずに、「どんな型でも」と書くのが、ジェネリクスです。第4回の &[T] の T も、第6回の Option<T> の T も、これです。

2つの値のうち、大きいほうを返す関数を、書いてみます。

fn largest<T>(a: T, b: T) -> T {
    if a > b { a } else { b }
}
error[E0369]: binary operation `>` cannot be applied to type `T`
 --> t7.rs:2:10
  |
2 |     if a > b { a } else { b }
  |        - ^ - T
  |        |
  |        T
  |
help: consider restricting type parameter `T` with trait `PartialOrd`
  |
1 | fn largest<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
  |             ++++++++++++++++++++++

T は、何の型かわからないので、> で比べられるか、わかりません。help が、「PartialOrd というトレイトを持つ型に、制限してください」と言っています。これが、先ほどのトレイト境界です。

fn largest<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", largest(3, 5));
    println!("{}", largest(2.5, -1.0));
    println!("{}", largest('a', 'z'));
    println!("{}", largest("apple", "banana"));
    println!("{}", largest(String::from("b"), String::from("a")));
}
5
2.5
z
banana
b

整数、小数、文字、文字列と、5種類の型で、同じ関数が使えました。PartialOrd を持たない自作の型を渡すと、止まります。

error[E0277]: can't compare `Item` with `Item`
  --> t8b.rs:12:13
   |
12 |     let c = largest(a, b);
   |             ^^^^^^^ no implementation for `Item < Item` and `Item > Item`
   |
   = help: the trait `PartialOrd` is not implemented for `Item`
note: required by a bound in `largest`
  --> t8b.rs:1:15
   |
 1 | fn largest<T: PartialOrd>(a: T, b: T) -> T {
   |               ^^^^^^^^^^ required by this bound in `largest`
help: consider annotating `Item` with `#[derive(PartialOrd)]`
   |
 5 + #[derive(PartialOrd)]
 6 | struct Item {

ジェネリックな構造体

構造体にも、T が書けます。

struct Pair<T> {
    a: T,
    b: T,
}

impl<T> Pair<T> {
    fn swap(self) -> Pair<T> {
        Pair { a: self.b, b: self.a }
    }
}

fn main() {
    let p = Pair { a: 1, b: 2 };
    let p = p.swap();
    println!("{} {}", p.a, p.b);
    let q = Pair { a: String::from("x"), b: String::from("y") };
    let q = q.swap();
    println!("{} {}", q.a, q.b);
}
2 1
y x

Pair<T> の a と b は、同じ型 Tです。違う型を入れると、エラーになります。

let p = Pair { a: 1, b: "x" };
error[E0308]: mismatched types
 --> t9b.rs:7:29
  |
7 |     let p = Pair { a: 1, b: "x" };
  |                             ^^^ expected integer, found `&str`

impl<T> Pair<T> の、最初の <T> は、「この impl の中で T を使う」という宣言です。

型ごとに、別の関数ができる

show<T: Describe> を、Item と Shape で呼ぶ、小さなプログラムを作って、コンパイルした実行ファイルの、関数の一覧を見ました(nm)。

0000000000014460 t t10::show::<t10::Item>
0000000000014500 t t10::show::<t10::Shape>

show が、型ごとに別の関数として、できていました(デバッグビルド、#[inline(never)] を付けて確認)。

Java のジェネリクスと並べる

Java にも、ジェネリクスがあります。トレイト境界にあたるのは、<T extends ...> です。

static <T extends Comparable<T>> T largest(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

largest(3, 5) は 5、largest("apple", "banana") は banana でした。Comparable を持たない自作クラスを渡すと、コンパイルエラーです。

J2.java:9: error: method largest in class J2 cannot be applied to given types;
        System.out.println(largest(new Item(), new Item()));
                           ^
  required: T,T
  found:    Item,Item
  reason: inference variable T has incompatible bounds
    upper bounds: Comparable<T>
    lower bounds: Item

違いが出るのは、基本の型です。

List<int> v = new ArrayList<>();
J3.java:4: error: unexpected type
        List<int> v = new ArrayList<>();
             ^
  required: reference
  found:    int

Java では、List<int> が書けず、List<Integer> と書きます。Rust の Vec<i32> は、そのまま書けます(第3回から使っています)。

また、Java では、型の引数が、実行時には区別されません。

List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass());
true

List<String> と List<Integer> が、実行時には、同じクラスでした。

標準のトレイト

ここまでに出てきた、Debug、Clone、Copy、Drop、From は、すべて、標準のトレイトです。

Debug と Display

println! の {:?} は Debug、{} は Display を使います。第5回の #[derive(Debug)] が、前者でした。Display を持たない型を {} で出そうとすると、エラーです。

println!("{}", item);
error[E0277]: `Item` doesn't implement `std::fmt::Display`
 --> u1.rs:8:20
  |
8 |     println!("{}", item);
  |               --   ^^^^ `Item` cannot be formatted with the default formatter
  |               |
  |               required by this formatting parameter
  |
help: the trait `std::fmt::Display` is not implemented for `Item`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

Debug は、derive で自動で付けられました。Display は、今回は自分で書きます。

use std::fmt;

struct Item {
    name: String,
    qty: u32,
}

impl fmt::Display for Item {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{} × {}", self.name, self.qty)
    }
}

fn main() {
    let item = Item { name: String::from("りんご"), qty: 3 };
    println!("{}", item);
    println!("{item}");
    let s = item.to_string();
    println!("{} 文字", s.chars().count());
}
りんご × 3
りんご × 3
7 文字

write! は、println! と同じ書式で、f に書き込みます。Display を実装すると、to_string() も使えるようになりました。Display のない型に to_string() を呼ぶと、Item: std::fmt::Display が満たされていない、という E0599 になります。

Clone と Copy

第3回で、「String を持つ型は、ムーブされる。clone で複製する」と見ました。自作の型で clone を呼ぶと、次のとおりです。

let a = Item { name: String::from("りんご"), qty: 3 };
let b = a.clone();
error[E0599]: no method named `clone` found for struct `Item` in the current scope
  |
  = note: the following trait defines an item `clone`, perhaps you need to implement it:
          candidate #1: `Clone`

#[derive(Clone)] を付けると、通ります。複製した b.name は、第3回と同じく、別のメモリでした(as_ptr() が、false)。

Copy は、Clone が前提です。

#[derive(Copy)]
struct P {
    x: i32,
}
error[E0277]: the trait bound `P: Clone` is not satisfied
 --> u3.rs:2:8
  |
1 | #[derive(Copy)]
  |          ---- in this derive macro expansion
2 | struct P {
  |        ^ the trait `Clone` is not implemented for `P`
  |
help: consider annotating `P` with `#[derive(Clone)]`

String のように、Copy でない値を持つ型には、Copy を付けられません。

#[derive(Clone, Copy)]
struct Item {
    name: String,
    qty: u32,
}
error[E0204]: the trait `Copy` cannot be implemented for this type
 --> u3b.rs:2:8
  |
1 | #[derive(Clone, Copy)]
  |                 ---- in this derive macro expansion
2 | struct Item {
  |        ^^^^
3 | name: String,
  |     ------------ this field does not implement `Copy`

第3回の「整数・小数・&str は Copy、String は Copy でない」と、同じ話です。Copy でないフィールドが1つでもあると、その型に Copy は付けられませんでした。整数だけの P は、付けられました(第5回)。

Drop

第3回から使っている impl Drop for Noisy は、Drop というトレイトの実装でした。スコープの終わりに、Rust が drop を呼びます。自分で a.drop() と呼ぶことは、できません。

let a = Noisy("a");
a.drop();
error[E0040]: explicit use of destructor method
  --> u4.rs:11:7
   |
11 |     a.drop();
   |       ^^^^ explicit destructor calls not allowed
   |
help: consider using `drop` function
   |
11 -     a.drop();
11 +     drop(a);

help のとおり、drop(a) と書くと、その場で捨てられます。

let a = Noisy("a");
let _b = Noisy("b");
drop(a);
println!("main の終わり");
drop: a
main の終わり
drop: b

drop(a) で、a がその場で捨てられ、b は、main の終わりで捨てられました。

From と ?

第6回で、? が、エラーの型を変換しようとして、止まりました。

error[E0277]: `?` couldn't convert the error to `String`
  |
  |               --------------^ the trait `From<ParseIntError>` is not implemented for `String`

From は、「ある型から、別の型を作る」トレイトです。第3回から使っている String::from("りんご") も、From です。第6回の help の一覧にも、String が From<&str> を実装していると出ていました。

自分のエラー型に From を実装すると、? が、自動で変換してくれます。

use std::fmt;
use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    Parse(ParseIntError),
    Zero,
}

impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> AppError {
        AppError::Parse(e)
    }
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::Parse(e) => write!(f, "数ではありません({e})"),
            AppError::Zero => write!(f, "0 は指定できません"),
        }
    }
}

fn parse_qty(s: &str) -> Result<u32, AppError> {
    let n = s.parse::<u32>()?;
    if n == 0 {
        return Err(AppError::Zero);
    }
    Ok(n)
}

fn main() {
    for s in ["3", "0", "abc"] {
        match parse_qty(s) {
            Ok(n) => println!("{s} -> {n}"),
            Err(e) => println!("{s} -> エラー: {e}"),
        }
    }
    println!("{:?}", parse_qty("abc"));
}
3 -> 3
0 -> エラー: 0 は指定できません
abc -> エラー: 数ではありません(invalid digit found in string)
Err(Parse(ParseIntError { kind: InvalidDigit }))

s.parse::<u32>()? の ParseIntError が、From で AppError::Parse(...) に変わり、Err(AppError) として返っています。第5回の enum、第6回の Result と ?、今回の From と Display が、1つにつながりました。

サンプル: 買い物リストに「元に戻す」を付ける

買い物リストから消した品物を、ごみ箱に入れて、後から戻せるようにします。ごみ箱は、ジェネリクスの Stack<T>(あとに入れたものから、先に出る入れ物)で作ります。

cargo new shop5
cd shop5

src/main.rs を、次のようにします。

use std::fmt;

struct Item {
    name: String,
    qty: u32,
}

impl fmt::Display for Item {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} × {}", self.name, self.qty)
    }
}

struct Stack<T> {
    items: Vec<T>,
}

impl<T> Stack<T> {
    fn new() -> Stack<T> {
        Stack { items: Vec::new() }
    }

    fn push(&mut self, x: T) {
        self.items.push(x);
    }

    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }

    fn len(&self) -> usize {
        self.items.len()
    }
}

fn print_all<T: fmt::Display>(items: &[T]) {
    for item in items {
        println!("  {item}");
    }
    println!("  ({} 件)", items.len());
}

fn main() {
    let mut list = vec![
        Item {
            name: String::from("りんご"),
            qty: 3,
        },
        Item {
            name: String::from("みかん"),
            qty: 5,
        },
        Item {
            name: String::from("ぶどう"),
            qty: 2,
        },
    ];
    let mut removed: Stack<Item> = Stack::new();

    println!("最初");
    print_all(&list);

    for _ in 0..2 {
        if let Some(item) = list.pop() {
            println!("削除: {item}");
            removed.push(item);
        }
    }
    println!("削除したあと(ごみ箱 {} 件)", removed.len());
    print_all(&list);

    if let Some(item) = removed.pop() {
        println!("元に戻す: {item}");
        list.push(item);
    }
    print_all(&list);

    let mut numbers: Stack<i32> = Stack::new();
    numbers.push(10);
    numbers.push(20);
    println!(
        "{:?} {:?} {:?}",
        numbers.pop(),
        numbers.pop(),
        numbers.pop()
    );
    print_all(&[1.5, 2.5]);
}
cargo run
最初
  りんご × 3
  みかん × 5
  ぶどう × 2
  (3 件)
削除: ぶどう × 2
削除: みかん × 5
削除したあと(ごみ箱 2 件)
  りんご × 3
  (1 件)
元に戻す: みかん × 5
  りんご × 3
  みかん × 5
  (2 件)
Some(20) Some(10) None
  1.5
  2.5
  (2 件)
部分意味
impl fmt::Display for Item{} で表示できるようにする(この節の Display)
struct Stack<T>どんな型でも入れられる、入れ物
Stack<Item>、Stack<i32>同じ Stack を、Item 用と i32 用に使う
fn pop(&mut self) -> Option<T>空のとき None(第6回)。最後の numbers.pop() が None
fn print_all<T: fmt::Display>(items: &[T])Display を持つ型の、スライス(第4回の &[T])なら何でも。Item の Vec も、小数の配列も通る

removed.pop() が Option を返すので、if let Some(item) = ...(第5回・第6回)で受けています。取り出した item は、list.push(item) で、list にムーブされます(第3回)。

cargo clippy は、指摘なしで通りました。cargo fmt --check も、整形のあとで、指摘なしで通りました。

歴史

年できごと
1988年10月Philip Wadler と Stephen Blott が、論文「How to make ad-hoc polymorphism less ad hoc」を書く。型クラス(type classes)を提案した
1989年1月その論文が、POPL(ACM のプログラミング言語の原理のシンポジウム)で発表される
2004年ジェネリクスが、Java に加わる(J2SE 5.0)

論文の要約は、型クラスを、「アドホック多相への新しい取り組み」と説明しています。Hindley/Milner の型システムを拡張し、演算子の多重定義などを扱えるようにするものです。この仕事は、Haskell の設計の中から出てきて、Haskell に入りました。論文の著者たちは、型クラスは、Haskell とは独立に評価されるべきで、Standard ML のような別の言語にも入れられる、とも書いています。

Java のジェネリクスについては、Wikipedia の記事に、型情報はコンパイル時に取り除かれ(型消去)、実行時には、型引数を決められない、とあります。基本の型は使えず、Integer のような包んだ型を使う、とも書かれています。

動作確認

確認できたこと

  • 上のコードと出力は、すべて、Linux(Rust 1.97.0、Cargo 1.97.0、edition 2024)で、実際に実行した結果です
  • Java のコードは、同じ環境の Java(OpenJDK 21)で、実際に実行した結果です。日本語のメッセージは、この環境のコンソールで文字化けしたので、Java の例は、英字のメッセージで実行しました
  • 13個のエラーのメッセージ(E0046、E0277 を4種類の形、E0599 を3種類の形、E0117、E0369、E0308、E0204、E0040)
  • トレイトの定義、impl、デフォルトメソッドとその上書き、<T: Trait> と &impl Trait、&dyn Trait の配列
  • 自分のトレイトを、i32 と String に実装できること。標準のトレイト(Display)を、標準の型(Vec<i32>)に実装すると E0117 になること
  • largest<T: PartialOrd> が、整数・小数・文字・&str・String で動くこと
  • show<T> が、型ごとに別の関数としてできること(nm の出力で確認。デバッグビルドの1回の実行)
  • Display を実装すると to_string() が使えること。Display がないと to_string() が E0599 になること
  • #[derive(Clone)] で複製した値が、別のメモリになること(as_ptr())
  • drop(a) が、その場で捨てること
  • From を実装した型で、? が、エラーの型を変換すること
  • Java の interface・default メソッド・<T extends Describe> の例。implements した interface のメソッドを書かないと、コンパイルエラー(does not override abstract method)になること。<T extends Comparable<T>> が、Comparable を持たないクラスでコンパイルエラーになること。List<int> が書けないこと。List<String> と List<Integer> の getClass() が同じになること
  • 買い物リストのサンプルが、cargo run で動くこと。cargo clippy と cargo fmt --check が、指摘なしで通ること

歴史の出典

  • 型クラスの論文のタイトル、著者、日付(1988年10月、POPL 1989年1月)、要約の内容は、Tufts 大学の Web サイトに置かれた論文の PDF の記述です。取得ツールが返した要約を通して確認しました。論文の全文は、読んでいません
  • Java のジェネリクスが 2004 年(J2SE 5.0)に加わったこと、型消去、基本の型が使えないことは、Wikipedia の「Generics in Java」の記述です。ミラーのページを使い、取得ツールの要約を通して確認しました。1つの出典で、他の資料とは、突き合わせていません

確認できていないこと

  • Rust のトレイトが、Haskell の型クラスから、どの程度影響を受けているか。この記事では、型クラスの論文の内容と、Rust のトレイトの動きを、別々に書いています
  • dyn で、どのメソッドが呼ばれるかを決める仕組み(実行時に決まる、など)。配列に別の型を入れて呼べることだけを、確認しました
  • nm で見た「型ごとに別の関数ができる」が、最適化をかけたビルド(--release)でも同じか
  • 「標準のトレイトを標準の型に実装できない」規則(孤児規則)の、細かい条件。E0117 のメッセージと、1つの例だけを確認しました
  • Java で、既存のクラスに、あとから interface を付けられるかどうか
  • Windows と macOS での実行結果

次回

次回は、ライフタイム入門と、総まとめの CLI(ファイル一覧ツール)です。第4回の E0106(missing lifetime specifier)と E0597(does not live long enough)、第2回の &'static str の 'static を回収します。入門編の最終回として、ここまでの所有権・借用・struct・enum・Result・トレイトを使った、ファイル一覧のコマンドラインツールを作ります。

連載の目次(予定)

入門編(8回)

  1. Rust とは / 環境構築 / Hello World
  2. 変数・型・関数(Java との対比)
  3. 所有権(ムーブ)
  4. 借用と参照
  5. 構造体・enum・match
  6. Result / Option とエラー処理
  7. トレイトとジェネリクス(今回)
  8. ライフタイム入門と、総まとめの CLI(ファイル一覧ツール)