はじめに
The Rust Programming Languageを頭から読み進めてみる。読みながら要点をまとめていった記事です。
- The Rust Programming Languageを読み進める(1〜2章)
- The Rust Programming Languageを読み進める(3章)
- The Rust Programming Languageを読み進める(4章)
- The Rust Programming Languageを読み進める(5章)
- The Rust Programming Languageを読み進める(6章)
7章:肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する
Cargoの構成は下記の画像のようになっている:
Cargoプロジェクトの構造 (Quoted from "Practical System Programming for Rust Developers," Link)
- パッケージ
- クレート(木枠)
-
Cargo.toml
に定義されているビルドの単位 - 実行バイナリとライブラリの2つに分かれる
-
- モジュール
- モジュールはクレートの要素に対して階層構造を持たせた仕組み
- パス
- モジュールの場所を示すための名前
7.1章:パッケージとクレート
- パッケージは、ある機能群を提供する1つ以上のクレート
- パッケージは、各クレートをどの様にビルドするかを定義した
Cargo.toml
を持つ - パッケージを作るコマンドと、作った後のツリーは下記の通り
$ cargo new my-project
.
└── my-project
├── Cargo.toml
└── src
└── main.rs
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-
my-project
という名前の、バイナリクレートを持っている状態-
src/main.rs
とsrc/lib.rs
があれば、クレートは2つになる - 実行バイナリ
- エントリポイントを持つ
-
main.rs
がクレートルート
- ライブラリクレート
- エントリポイントを持たない
-
lib.rs
がクレートルート(上記ルートでは定義なし)
-
7.2章:モジュールを定義して、スコープとプライバシーを制御
- モジュールはクレートの要素に対して階層構造を持たせた仕組み
- コードをグループ化することで、可読性と再利用性の向上に貢献
- 以下はモジュールの定義とモジュールツリーの例
-
mod
を使って定義し、本体を{}
で囲む -
pub mod xxx
やpub fn xxx
のように書くことで、public/privateの制御も可能
-
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
7.3章:モジュールツリーの要素を示すためのパス
絶対パスと相対パス
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {
println!("Hi!");
}
}
}
pub fn eat_at_restaurant() {
// 絶対パス
crate::front_of_house::hosting::add_to_waitlist();
// 相対パス
front_of_house::hosting::add_to_waitlist();
}
fn main(){
eat_at_restaurant();
}
Hi!
Hi!
- 絶対パスは、
crete
(ルート)から::
でパスを繋いでいく - 相対パスは、今のモジュールから
::
でパスを綱で行く - Rustは基本的な要素がデフォルトでPrivate
- なので、公開する場合は
pub
キーワードを使う
- なので、公開する場合は
相対パスをsuperで始める
fn serve_order() {}
pub mod back_of_house {
pub fn fix_incorrect_order() {
cook_order();
super::serve_order();
println!("fix incorrect order");
}
fn cook_order() {
println!("cook order");
}
}
fn main(){
back_of_house::fix_incorrect_order();
}
cook order
fix incorrect order
-
親モジュールから始まる相対パスは、
super`をつけることで実装できる -
main()
では、次の通りfix_incorrect_order()
が呼び出される- 内部の関数の
cook_order
が呼ばれる -
super
、つまりback_of_hosue
の親モジュールであるcrate
を見つけ、serve_order
を探して見つけて呼ぶ - print文が呼ばれる
- 内部の関数の
構造体とenumを公開する
mod back_of_house {
#[derive(Debug)]
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
#[derive(Debug)]
pub enum Appetizer {
Soup,
Salad,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
let mut meal = back_of_house::Breakfast::summer("Rye");
println!("{:?}", meal);
meal.toast = String::from("Wheat");
println!("{:?}", meal);
// pubを付けない限りエラー
// println!("{:?}", meal.seasonal_fruit);
// meal.seasonal_fruit = String::from("blueberries");
println!("{:?}", back_of_house::Appetizer::Soup);
println!("{:?}", back_of_house::Appetizer::Salad);
}
fn main() {
eat_at_restaurant();
}
Breakfast { toast: "Rye", seasonal_fruit: "peaches" }
Breakfast { toast: "Wheat", seasonal_fruit: "peaches" }
Soup
Salad
- 構造体
Breakfast
はpub
を付けることで公開できるが、フィールドは非公開のままなので、フィールドごとに公開の有無を決めることが出来る - 公開されなかったフィールドは、アクセスすることや書き換えることは不可
-
enum
はpub
で公開すると、フィールドは公開になる- 背景としては、1つ1つのバリアントに
pub
をつけるのが面倒だし、公開されていない方が不変
- 背景としては、1つ1つのバリアントに
7.4章:useキーワードでパスをスコープに持ち込む
useを使う
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {
println!("Hi!");
}
}
}
use crate::front_of_house::hosting;
use std::collections::HashMap;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
fn main() {
eat_at_restaurant();
let mut map = HashMap::new();
map.insert(1, 2);
println!("{:?}", map);
}
Hi!
Hi!
Hi!
{1: 2}
- 7.3章では
front_of_house::hosting::add_to_waitlist();
と呼び出しが長かったが、use
を使うとシンボリックリンクを定義でき、hosting::add_to_waitlist();
だけで済む -
use crate::front_of_house::hosting::add_to_waitlist;
と定義して、add_to_waitlist();
と端的に呼び出すのは慣例ではない。なぜなら、どこのモジュールの話なのかが分からなくなるため -
pub use crate::front_of_house::hosting;
とpub use
の定義により外部のコードからアクセスが可能 -
HashMap
構造体をスコープに定義しているが、この書き方が慣例
新しい名前をasキーワードで与える
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
println!("This is function1!");
Ok(())
}
fn function2() -> IoResult<()> {
println!("This is function2!");
Ok(())
}
fn main() {
println!("Hello world!");
if let Err(e) = function1() {
println!("Error in function1: {:?}", e);
}
if let Err(e) = function2() {
println!("Error in function2: {:?}", e);
}
}
Hello world!
This is function1!
This is function2!
- 同じ型を複数使いたい時は、親モジュールを定義する必要がある
-
fn function1() -> Result
とfn function2() -> Result
だと区別ができない - なので
fn function1() -> fmt::Result
とfn function2() -> io::Result<()>
のように定義する
-
- 上記サンプルのように、
as
と新しいローカル名(エイリアス)を指定すれば名前の衝突を避けられる
外部のパッケージを使う
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..101);
println!("rand number: {}", secret_number);
}
rand number: 54
-
Cargo.toml
に追加したクレートはダウンロードされて使用可能になる - サンプルの例は、クレート
rand
のRng
トレイトをスコープに持ち込み、rand::thread_rng()
関数を呼び出している状態
useのネスト
use std::{cmp::Ordering, io};
use std::io::{self, Write};
-
use
を使う時に、use std::cmp::Ordering;
,use std::io;
と書いていくと長くなる - 中括弧でネストすることで、宣言文を削減できる
glob演算子
use std::collections::*;
全ての公開要素をスコープに持ち込みたい時は、glo演算子*
を続けて書く
7.5章:モジュールを複数のファイルに分割する
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
上記コードについて、下記のように複数のファイルに分割可能
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
pub mod hosting {
pub fn add_to_waitlist() {}
}