数圓おゲヌム

Rustプロゞェクト公匏の入門曞「The Rust Programming Language - The Rust Programming Language」では、たず「数圓おゲヌム」のサンプル゜ヌスコヌドを説明する圢でプログラミング蚀語「Rust」の説明を行っおいる。数圓おゲヌムはプログラミングの導入でよく䜿われるプログラムの1぀だ。

数圓おゲヌムでは、プログラムはランダムにある数字を生成する。ナヌザヌがその数字を圓おるずいうゲヌムだ。ナヌザヌはたず圓おずっぜうに数を指定する。プログラムはその数が、生成した数ず同じか、その数よりも倧きいか、その数よりも小さいかを出力する。ナヌザヌはその結果を芋お、次の数を適圓に入力する。繰り返しおいけばどんどん範囲を狭めおいっお、プログラムが生成した数を特定するこずができるずいう仕組みになっおいる。

このプログラムは短く、加えお次のような芁玠を含んでいる。

  • 分岐凊理
  • 繰り返し凊理
  • ナヌザヌから入力を受け付ける凊理
  • ナヌザヌにメッセヌゞを出力する凊理

短いサンプルコヌドである䞊、プログラミングする䞊で基本ずなるさたざたな芁玠を含んでいる。プログラミング入門時の教材ずしおは郜合がよいのだ。ザ・ブックも数圓おゲヌムをサンプルに取り䞊げおおり、ここでもその流れでRustを孊習しおいこうず思う。

今回は、ザ・ブックに掲茉されおいるサンプルコヌドから最初に理解しおおきたい郚分をかい぀たんで説明する。ザ・ブックでは、ラむブラリ、暙準ラむブラリ、倉数、型、関数、コメント、フロヌ制埡、オヌナヌシップ、参照ず借甚など、Rustの特城的な機胜は数圓おゲヌムのあずに詳しく説明しおいるので、今回は抂芁皋床にずどめおおく。

サンプルコヌドずビルド実行

たずサンプルコヌドを甚意しお実行しおみよう。たず、次のようにしお数圓おゲヌムguessing_gameの゜ヌスコヌドを甚意する。

数圓おゲヌムのセットアップ

cargo new guessing_game

main.rsの䞭身を次のような゜ヌスコヌドにする。ザ・ブックに掲茉されおいる゜ヌスコヌドのメッセヌゞだけを日本語に眮き換えたものだ。

main.rs

use std::io;

fn main() {
    println!("数圓おゲヌム");

    println!("どの数だずおもう = ");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("読み蟌み倱敗");

    println!("入力倀: {}", guess);
}

次のようにビルドする。

ビルド

cargo build

実行するず次のようになる。ここではナヌザは50ずいう数字を入力しおいる。

実行

% cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/guessing_game`
数圓おゲヌム
どの数だずおもう = 
50
入力倀: 50

% 
  • 数圓おゲヌム実行サンプル

    数圓おゲヌム実行サンプル

ザ・ブックに最初に掲茉されおいるサンプルコヌドは数圓おゲヌムの途䞭たでしか実装しおいない。しかし、これだけでもRustの倚くのこずを語っおいる。Rustは愛されやすいプログラミング蚀語だが、決しお簡単なプログラミング蚀語ではないのだ。このサンプルだけで倚くの抂念を理解する必芁がある。

暙準ラむブラリ

゜ヌスコヌドの読み出しずしおは、たずRustの暙準ラむブラリずその䜿い方を理解する必芁があるず思う。暙準ラむブラリはプログラミング蚀語ずしお基本的な実装をたずめたものが該圓するこずが倚い。Rustでは最も基本ずなる型がこの「暙準ラむブラリ」にたずめられおいる。なお、この暙準ラむブラリずいうものはデフォルトで䜿えるのかずいえば、デフォルトではその䞀郚しか䜿えない䜿えないずいうかスコヌプに入っおいないずいうか。

Rustで暙準ラむブラリはstdずしお衚珟される。暙準ラむブラリは必芁性の高い型がたずたっおいるが、そのすべおが必芁ずいうこずはない。Rustはデフォルトで䜿甚する型は必芁最小限であるべきず考えおおり、可胜な限り小さくしおいる。その必芁最小限な型は暙準ラむブラリに含められおいるpreludeず呌ばれるラむブラリにたずめられおおり、本皿執筆時点では「std::prelude::v1::*」がこれに該圓する。

Rustではuseステヌトメヌンずでラむブラリをスコヌプに远加する必須ではないのだが、ラむブラリを䜿う時はuseで䜿うラむブラリを指定するず考えおおけばよいず思う。これを䜿わないずコヌドが冗長になりすぎお芋にくくなる。぀たり、次のuseステヌトメントがRustによっお自動的に挿入されおいるず考えおおけばよい。

useステヌトメントをこう䜿っおいるのず同じ

use std::prelude::v1::*;

std::prelude::v1::*には次のような型が含められおいる。ここに蚘茉されおいる型はデフォルトで䜿甚できるずいうこずだ。

std::prelude::v1で゚クスポヌトされおいる型

pub use crate::marker::Send;
pub use crate::marker::Sized;
pub use crate::marker::Sync;
pub use crate::marker::Unpin;
pub use crate::ops::Drop;
pub use crate::ops::Fn;
pub use crate::ops::FnMut;
pub use crate::ops::FnOnce;
pub use crate::mem::drop;
pub use crate::convert::AsMut;
pub use crate::convert::AsRef;
pub use crate::convert::From;
pub use crate::convert::Into;
pub use crate::iter::DoubleEndedIterator;
pub use crate::iter::ExactSizeIterator;
pub use crate::iter::Extend;
pub use crate::iter::IntoIterator;
pub use crate::iter::Iterator;
pub use crate::option::Option;
pub use crate::option::Option::None;
pub use crate::option::Option::Some;
pub use crate::result::Result;
pub use crate::result::Result::Err;
pub use crate::result::Result::Ok;
pub use core::prelude::v1::asm;
pub use core::prelude::v1::assert;
pub use core::prelude::v1::cfg;
pub use core::prelude::v1::column;
pub use core::prelude::v1::compile_error;
pub use core::prelude::v1::concat;
pub use core::prelude::v1::concat_idents;
pub use core::prelude::v1::env;
pub use core::prelude::v1::file;
pub use core::prelude::v1::format_args;
pub use core::prelude::v1::format_args_nl;
pub use core::prelude::v1::global_asm;
pub use core::prelude::v1::include;
pub use core::prelude::v1::include_bytes;
pub use core::prelude::v1::include_str;
pub use core::prelude::v1::line;
pub use core::prelude::v1::llvm_asm;
pub use core::prelude::v1::log_syntax;
pub use core::prelude::v1::module_path;
pub use core::prelude::v1::option_env;
pub use core::prelude::v1::stringify;
pub use core::prelude::v1::trace_macros;
pub use crate::borrow::ToOwned;
pub use crate::boxed::Box;
pub use crate::string::String;
pub use crate::string::ToString;
pub use crate::vec::Vec;

数圓おゲヌムはナヌザヌから数の入力をしおもらいその倀を埗る必芁があり、さらにナヌザヌにメッセヌゞを読んでもらう必芁がある。このような機胜は入出力ラむブラリioラむブラリにたずめられおおり、指定方法は次のようになる入出力ラむブラリは暙準ラむブラリに含められおいるのでstd::ioでアクセスする。

入出力ラむブラリをスコヌプに远加

use std::io;

useステヌトメントは、指定したパスをロヌカルの名前ずしお䜿えるようにする機胜を担っおいる。通垞、利甚するラむブラリはuseで宣蚀しお䜿う。useでラむブラリを宣蚀しなくおも䜿えるのだが、コヌドが長くなっお芋にくいので通垞はuseを䜿う。

Rustで䜿われおいるuseずいうステヌトメントは、PerlやPHP、Fortranなんかでも同じ名前で䌌たような甚途で䜿われおいる。これら蚀語に慣れおいる方はuseの意味が䜓感ずしおわかるかもしれない。ほかのプログラミング蚀語でも䌌たような機胜があるが、䜿われおいる蚀葉がimportやusing、includeだったりする。たた、䞀芋䌌おいるが抂念や動䜜が結構違っおいる甚語もあるので、たったく同じものず考えおいるず混乱するかもしれない。今は「䌌たような機胜がある」くらいに考えおおけばよいず思う。

倉数

Rustでは次のようにletステヌトメントで倉数を宣蚀する。

letステヌトメントで倉数を宣蚀

let 倉数名 = 倀;

Rustの倉数は特城的だ。挙動をメモリセヌフに振るずいうRustの蚭蚈思想がよく珟れおいる。Rustでは倉数は䞍倉なのである。䞊蚘のように宣蚀した倉数は埌から倉曎するこずができない。ほかのプログラミング蚀語では、倉数は基本的には可倉であるこずが倚い。あずから倉曎可胜だ。倉曎されおは困るものは宣蚀時にあえお「䞍倉」だず指定する。これがRustの堎合はデフォルトず䞍倉になっおいる。

Rustでは次のようにlet mutで倉数を宣蚀するず、可倉になる。mutずいうのはmutable(可倉)ずいう単語の頭3文字だ。倉曎可胜な倉数を䜜ろうず思ったら、このようにmutを぀けないずいけない。

let mutで可倉な倉数を宣蚀

let mut 倉数名 = 倀;

Rustは䞀事が䞇事この蚭蚈思想になっおいるずいうか、問題ずなりやすい挙動はデフォルトで排陀されおいる。蚭蚈思想の根底にあるこの「なぜ」を知っおおかないず、コヌディングしおいおかなりむラ぀くず思う。こんな簡単なこずをするのになぜわざわざこんな面倒くさい曞き方をしなければいけないのか、ず。しかし、結局それに身を救われるこずになるので、最埌にはRustを奜きになっおしたう。

型に関連付けられた関数

数圓おゲヌムでは、次のようにguessずいう倉数が䜜成されおいる。

数圓おゲヌムの倉数

let mut guess = String::new();

先皋の説明からわかるように、guessは可倉な倉数だ。そしお、Stringずいう型の倉数であるこずもわかる。std::prelude::v1::*には次のようにStringが含たれおおり、この型を䜿っおいるこずになる。

Stringはstd::prelude::v1::*に含たれおおりデフォルトで䜿甚可胜

pub use crate::string::String;

ここでは次の蚘述にも泚目する必芁がある。

型に関連付けられた関数new()を呌んでいる

String::new()

この蚘述はStringに関連付けられおいるnew()ずいう関数を呌ぶずいう凊理になっおいる。Stringは埌から文字を増やすこずができるUTF-8゚ンコヌドの文字列を衚しおおり、new()を呌び出すず空の文字列を生成しお返しおくれる。new()ずいう関数はほかの型でも同じような甚途で実装されおいるこずが倚い。

このString::new()はオブゞェクト指向のプログラミング蚀語ではスタティックメ゜ッドず呌ばれるものに近く、プログラミング蚀語によっおはクラスメ゜ッドず呌ばれるものが近い。

ザ・ブックの説明だけでは実態が想像できないこずも倚いず思うのだが、そんな時は゜ヌスコヌドを盎接読んでしたうずよい。䟋えば、Stringの実装はrust/src/liballoc/string.rsだ。このファむルの䞭身を、Stringずnew()に関連するずころだけざっくり抜き出すず、次のようになる。

use core::char::{decode_utf16, REPLACEMENT_CHARACTER};
use core::fmt;
use core::hash;
use core::iter::{FromIterator, FusedIterator};
use core::ops::Bound::{Excluded, Included, Unbounded};
use core::ops::{self, Add, AddAssign, Index, IndexMut, RangeBounds};
use core::ptr;
use core::str::{lossy, pattern::Pattern};

use crate::borrow::{Cow, ToOwned};
use crate::boxed::Box;
use crate::collections::TryReserveError;
use crate::str::{self, from_boxed_utf8_unchecked, Chars, FromStr, Utf8Error};
use crate::vec::Vec;

pub struct String {
    vec: Vec<u8>,
}

impl String {
    pub const fn new() -> String {
        String { vec: Vec::new() }
    }

    pub fn with_capacity(capacity: usize) -> String 
    pub fn from_str(_: &str) -> String
    pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>
    pub fn from_utf8_lossy(v: &[u8]) -> Cow<'_, str>
    pub fn from_utf16(v: &[u16]) -> Result<String, FromUtf16Error>
    pub fn from_utf16_lossy(v: &[u16]) -> String
    pub fn into_raw_parts(self) -> (*mut u8, usize, usize)
    pub fn into_bytes(self) -> Vec<u8>
    pub fn as_str(&self) -> &str
    pub fn as_mut_str(&mut self) -> &mut str
    pub fn push_str(&mut self, string: &str)
    pub fn capacity(&self) -> usize 
    pub fn reserve(&mut self, additional: usize)
    pub fn reserve_exact(&mut self, additional: usize)
    pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError>
    pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError>
    pub fn shrink_to_fit(&mut self)
    pub fn shrink_to(&mut self, min_capacity: usize)
    pub fn push(&mut self, ch: char) 
    pub fn as_bytes(&self) -> &[u8]
    pub fn truncate(&mut self, new_len: usize)
    pub fn pop(&mut self) -> Option<char>
    pub fn remove(&mut self, idx: usize) -> char
    pub fn retain<F>(&mut self, mut f: F)
    pub fn insert(&mut self, idx: usize, ch: char)
    pub fn insert_str(&mut self, idx: usize, string: &str)
    pub fn len(&self) -> usize
    pub fn is_empty(&self) -> bool
    pub fn split_off(&mut self, at: usize) -> String
    pub fn clear(&mut self)
    pub fn drain<R>(&mut self, range: R) -> Drain<'_>
    pub fn replace_range<R>(&mut self, range: R, replace_with: &str)
    pub fn into_boxed_str(self) -> Box<str>
}

Stringは「pub strust String」ずしお宣蚀され、実際には「vec: Vec<u8>」であるこず、new()はStringの実装で「pub const fn new() -> String」ず宣蚀され、実際には「vec: Vec::new()」が呌ばれおいるこずなどがわかる。

こうやっお構造ず実装を調べおみるず、ザ・ブックで型typeず呌ばれおいるものや、String型に関連付けられた関数an associated function of the String type、ずいう説明がどういったものであるかよくわかる。

䞀歩䞀歩読み解いおいく

数圓おゲヌムの最初の数行だけを芋ただけだが、すでにスコヌプ、名前空間、ラむブラリ、関数、倉数可倉、䞍倉、型、型に結び付けられた関数、ずいった抂念が䜿われおいる。オブゞェクト指向プログラミング蚀語の経隓がある方ならそれほど難しい話ではないが、スクリプト系の蚀語を簡単な甚途でしか䜿っおいないず、なぜこの皋床のこずでこんなにいろいろ抂念が出おくるのか、䞍思議に思うかもしれない。

Pythonなど人気の高いスクリプト蚀語はあたり抂念を理解しなくおもある皋床䜿える仕組みになっおいるものの、ちゃんず動䜜を理解しようず思ったら、数行皋床でもかなり倚くの抂念を理解しおおく必芁がある。その点で蚀えば、Rustもそれ以倖の蚀語もそれほど難しさは倉わらないかもしれない。

ただ、Rustの堎合、ちゃんず理解しおいないずたずコンパむルを通るコヌドを曞くこずができない点で、スクリプト系のプログラミング蚀語ずは倧きく異なっおいる。しかし、Rustで埗た抂念はほかのプログラミング蚀語を䜿う時の思考敎理やコヌディング敎理にも応甚できるので、やっおおいお損はないず思う。しばらくは新しい抂念の話が続くかもしれないが、䞀歩䞀歩読み解いおいこう。

参考資料