LoginSignup
46
41

More than 5 years have passed since last update.

Rust勉強してみたー前半戦

Last updated at Posted at 2016-07-26

概要

最近Rustの話をよく聞くようになったので、どんなものか触ってみた。前半戦。後半戦も後日書きます。
バージョンは1.10.0でCargoは使っていない。頑張ってコマンドプロンプトでrustcしてた。IDEはよ。

勉強に使ったサイトは以下。あとは適当にググりながらやった。
プログラミング言語Rust
Rust Documentation(英語。つらい。)

よくよく見直したら、以下の解説に類似した書き方をしている所が多かったので、参考にした所として追加しておきます。
Rustのポインタ(所有権・参照)・可変性についての簡単なまとめ

前半戦はプログラミング言語Rustの4.1~4.15と3.1をやりました。
軽く構文を知ってから実際に簡単なゲームを作った、という流れ。

注意:実装しているときに引っかかったことだけ書くので、言語の強みとか記述方法とかは上記サイトを見てください。

後半戦はこちら

引っかかり点その1-所有権

この言語の特徴点の一つ。
Rustでは変数は格納された値に対する所有権を持ち、
別の変数に値を移すと一緒に所有権も移動する。
そして、所有権を失った変数を使用すると、エラーになる。(実行時ではなく、コンパイル時)

別段難しい概念ではないが、以下のようなコードを頻繁に書いてしまい、
コンパイラに怒られる怒られる。慣れるまで大変。

main.rs
struct Point
{
    x: i32,
    y: i32,
}

fn Print( p: Point )
{ 
    println!( "x = {}, y = {}", p.x, p.y );
}

fn main()
{
    let a: Point = Point{ x: 10, y: 15 };

    Print( a );

    println!( "x = {}, y = {}", a.x, a.y );
}
CompileResult
error: use of moved value: `a.x` [E0382]
println!( "x = {}, y = {}", a.x, a.y );
                            ^~~
error: use of moved value: `a.y` [E0382]
println!( "x = {}, y = {}", a.x, a.y );
                                 ^~~

これを解決する方法として、二種類ある。

一つはCopyトレイトを書くこと。
やってみるとわかるが、上記コードの型をi32やusizeといったプリミティブ型に変えると、コンパイルはすんなり通る。
理由は、プリミティブ型にはすでにCopyトレイトが実装されているから。
なので、ユーザ定義型であるPointにCopyトレイトを実装すれば、コンパイル通ります。
トレイトをどう実装するかは私も知らない(後半戦で勉強します。)

もう一つは、以下に書く「借用」なのだが…

引っかかり点その2-借用

ある変数を関数に引数として渡したい…でも所有権は失いたくない…!
という時に便利なのが、借用(borrowing)という仕組みでして。

先ほどのコードも以下のようにするとあら不思議。
コンパイルを通るようになります。

main.rs
struct Point
{
    x: i32,
    y: i32,
}

fn Print( p: &Point )
{ 
    println!( "x = {}, y = {}", (*p).x, (*p).y );
}

fn main()
{
    let a: Point = Point{ x: 10, y: 15 };

    Print( &a );

    println!( "x = {}, y = {}", a.x, a.y );
}

関数の呼び出し口や仮引数に&が付いていたり、仮引数へのアクセスの仕方が変わっていますね。
これにより、所有権を移すことなく、値の参照が可能になります。
C++でいうポインタとか参照とかと同じ感覚。
コピーを作るのではなく、本体は一つでそれにアクセスできる変数を増やした感じ。

この辺りまでは特に面倒ではないのですが。
可変性が関わってくると面倒なことに。
以下をご覧ください。

main.rs
struct Point
{
    x: i32,
    y: i32,
}

//参照変数pがイミュータブル、且つ指定型もイミュータブルなので、値を参照することしかできない.
fn Print_ii( p: &Point )
{
    //エラー.参照先の変更は不可.
    //let pp = Point{ x: 0, y: 0 };
    //p = &pp;

    //エラー.参照先の値の変更も不可
    //(*p).x = 10;

    println!( "x = {}, y = {}", (*p).x, (*p).y );
}

//参照変数pがイミュータブル、指定型はミュータブルなので、値の変更はできる.
fn Print_im( p: &mut Point )
{
    //エラー.参照先の変更は不可.
    //let pp = Point{ x: 0, y: 0 };
    //p = &mut pp;

    //OK.参照先の値は変更可能.
    //(*p).x = 10;

    println!( "x = {}, y = {}", (*p).x, (*p).y );
}

//参照変数pがミュータブル、且つ指定型もミュータブルなので、何でもできる.
fn Print_mm( mut p: &mut Point )
{ 
    //OK.参照先の変更可能.(まぁそもそもこれはlifetime的にダメだけど).
    //let mut pp = Point{ x: 0, y: 0 };
    //p = &mut pp;

    //OK.参照先の値の変更も可能.
    //(*p).x = 10;

    println!( "x = {}, y = {}", (*p).x, (*p).y );
}

fn main()
{
    let mut a: Point = Point{ x: 1, y: 15 };

    Print_ii( &a );
    Print_im( &mut a );
    Print_mm( &mut a );

    println!( "x = {}, y = {}", a.x, a.y );
}

C++的に書くなら、const int* const p 的な話。
わかりづらいんじゃ。

引っかかり点その3-パターンマッチ

これが一番?????ってなった所。

Rustの特徴の一つに、強力なパターンマッチがある。
構造体宣言っぽい書き方でメンバを定義できる列挙型と組み合わせると以下のようなことができる。

main.rs
enum TYPE
{
    TYPE_A( i32, u32, usize ),
    TYPE_B{ a: char, b: i32 },
    TYPE_C,
}

fn main()
{
    let t: TYPE = TYPE::TYPE_A( 1,2,3 );

    match t
    {
        TYPE::TYPE_A(a,b,c)     => { println!( "{}, {}, {}", a, b, c ); },
        TYPE::TYPE_B{ a, b }    => { println!( "{}, {}", a, b );        },
        TYPE::TYPE_C            => { println!( "TYPE_C" );              },
    };
}

列挙型もなかなか気持ち悪くてアレだけど、
変数宣言していないのに変数に値を格納できるとか変態すぎやろ。
デストラクチャリングというらしいけど、しっくりくるまでに一番時間かかった。
まぁ、こういうもんだと思えば、難しいことは全くない。

総括

以下の点らへんから、最初こそ戸惑うものの、慣れると安心してコーディングできると感じた。
・コンパイル時点で重大なバグを防いでくれる点
・変数のデフォルトがイミュータブル(不可変)な点、
・上記所有権周り

が、慣れるまでは結構コンパイラに怒られる。俺は逆切れしたった。やりたいことがすぐにできない。
他の言語だと大丈夫なことがエラー扱いになったり、Warning扱いになったりするので、
初心者に初めて触らせる言語、とかには向いていないと思う。

日頃からconstを頻繁に使っている人とかは入りやすいのかな。

どうでもよいこと

スネークケースで書かないと、コンパイル時にWarning吐くんだけど、これいる?
せめてデフォルトはOFFにしてほしいんだけど…

それとおまけで、プログラミング言語Rustの3.1をサイトをほとんど見ずに実装したコードを置いておきます。
サイト見てみたら分かるが、もっと分かりやすく簡潔に書けるので、
なんというかすごくアレです。一応変なバグはないはず。Warningめっちゃ出るけど。
(主にスネークケースと不要な()に関するWarning。大きなお世話じゃ。)

main.rs
use std::io;
use std::str;

enum COMP_RESULT
{
    COMP_SMALL,
    COMP_BIG,
    COMP_SAME,
    COMP_INVALID,
}

fn IsValid( res: &COMP_RESULT ) -> bool
{
    let mut bValid: bool = false;

    match (*res)
    {
        COMP_RESULT::COMP_SMALL => { bValid = true; },
        COMP_RESULT::COMP_BIG   => { bValid = true; },
        COMP_RESULT::COMP_SAME  => { bValid = true; },
        _                       => { bValid = false; },
    };

    bValid
}

fn bConvertS2N( str: &String, num: &mut i32, min: &i32, max: &i32 ) -> bool
{
    (*num) = -1;

    let mut chs     : str::Chars    = (*str).chars();
    let mut bValid  : bool          = true;

    //一文字ずつ確認しながら数値に変換.
    while( (*num) <= (*max) )
    {
        //文字取得.
        let resultNext: Option<char> = chs.next();

        //正常終了チェック.
        match resultNext
        {
            Option::Some( n ) =>
            {
                if( n == '\0' ){ break; }
                if( n == '\n' ){ break; }
                if( n == '\r' ){ break; }
            }
            Option::None => { break; },
        };

        //初期値でここを抜けたら0を代入しておく(計算の簡単化と不正検知のため).
        if( (*num) < 0 ){ (*num) = 0; }

        //桁を上げる.
        (*num) *= 10;

        //加算.
        match resultNext
        {
            Option::Some( n ) => 
            { 
                match n
                {
                    '0' => { (*num) += 0; },
                    '1' => { (*num) += 1; },
                    '2' => { (*num) += 2; },
                    '3' => { (*num) += 3; },
                    '4' => { (*num) += 4; },
                    '5' => { (*num) += 5; },
                    '6' => { (*num) += 6; },
                    '7' => { (*num) += 7; },
                    '8' => { (*num) += 8; },
                    '9' => { (*num) += 9; },
                    _   => { bValid = false; break; },  //異常終了.
                };
            },
            _ => { /*無視.*/ },
        };
    };

    //範囲に入っていないので不正.
    if( !( (*min) <= (*num) && (*num) <= (*max) ) ){
        bValid = false;
    }

    bValid
}

fn main()
{
    let rangeMin: i32 = 0;
    let rangeMax: i32 = 100;

    //ウェルカムメッセージ.
    println!( "Guess the number!" );
    println!( "Let's hit number!({}~{})", rangeMin, rangeMax );

    //目標値(最終的にはランダムにする).
    let purposeNum : i32 = 50;

    //正解するまで繰り返す.
    loop 
    {
        //入力.
        println!( "Input number!" );
        let mut guess: String = String::new();
        let inputResult: io::Result<usize> = io::stdin().read_line( &mut guess );

        //入力有効チェック.
        match inputResult
        {
            Ok( n ) => { /*OK.特に何もしない.*/ },
            _       => { println!( "Input Miss! Retry!" ); continue; },
        };

        //文字列->数値.
        let mut guessNum: i32 = 0;
        if( !bConvertS2N( &guess, &mut guessNum, &rangeMin, &rangeMax ) ){
            println!( "Input invalid! Retry!" );
            continue;
        }

        //比較.
        let mut compResult: COMP_RESULT = COMP_RESULT::COMP_INVALID;
        if( purposeNum > guessNum ){
            compResult = COMP_RESULT::COMP_SMALL;
        }
        else if( purposeNum < guessNum ){
            compResult = COMP_RESULT::COMP_BIG;
        }
        else{
            compResult = COMP_RESULT::COMP_SAME;
        }

        //比較結果チェック.
        if( !IsValid( &compResult ) ){
            println!( "Comp error! Retry!" );
            continue;
        }

        //結果に基づいて続けるか決める.
        match compResult
        {
            COMP_RESULT::COMP_SAME  => { println!( "Clear!" ); break;   },
            COMP_RESULT::COMP_SMALL => { println!( "Small! Retry!" );   },
            COMP_RESULT::COMP_BIG   => { println!( "Big! Retry!" );     },
            _                       => { println!( "Assert! Retry!" );  },
        };
    }
}
46
41
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
41