2016-07-13 14 views
1

私は小さな戦略ゲームを書いていますが、循環リンクリストの実装に問題があります。安全な錆で循環リンクリストを作成できません。安全でないバージョンがクラッシュする

ゲームには、ゲームが終了するまで、1つずつラウンドしてアクションを取っている複数の人が含まれます。しかし、これは、各要素が次のプレイヤーへの参照を持つプレーヤーである循環リンクリストを使用することによって行うことができます。構造は次のようである:

#[derive(Debug, Clone)] 
struct Player { 
    name: String, 
    killed: bool, 
    next: Option<Box<Player>>, 
} 

私はまた、現在のアクティブプレイヤーへのポインタをしたいし、それの状態を変更することができるが、私は錆は私が同じオブジェクトに2つの可変の参照を持ってすることはできませんだと思います各プレイヤーはすでに次のプレイヤーへの変更可能な参照を持っているからです。

私が思いついたのは、以前のプレーヤーが所有していて、現在のプレーヤーを指しているBoxへの簡単な変更可能な参照を使用できるということです。エラーは可変性についてもですが、私はそれを修正する方法が分からない

fn main() { 
    let mut p3: Player = Player { 
     name: "C".to_string(), 
     killed: false, 
     next: None, 
    }; 
    let mut p2: Player = Player { 
     name: "B".to_string(), 
     killed: false, 
     next: Some(unsafe { Box::from_raw(&mut p3) }), 
    }; 
    let mut p1: Player = Player { 
     name: "A".to_string(), 
     killed: false, 
     next: Some(unsafe { Box::from_raw(&mut p2) }), 
    }; 
    p3.next = Some(unsafe { Box::from_raw(&mut p1) }); 
    println!("GAME STARTED!"); 
    let mut current_player = p3.next.as_mut().unwrap(); 
    let mut count = 0; 
    while count < 10 { 
     println!("Player to kill/save {}", current_player.name); 
     (*current_player).killed = !(*current_player).killed; 
     println!("Player {} is killed: {}", 
       (*current_player).name, 
       (*current_player).killed); 
     current_player = (*current_player).next.as_mut().unwrap(); 
     count = count + 1 
    } 
    println!("End!"); 
} 

:私は、問題が発生したシンプルな主な機能を書きました。循環リンクリストと現在のプレーヤーへのポインタを使用するのではなく、Rustでアイデアを実装するより良い方法があるのだろうかと思います。たぶん別の構造に切り替えるべきでしょうか?

エラーメッセージは、ここで最初の数行はかなり長いですされています。私はBoxの不変の参照を返しますas_ref()as_mut()方法を変更し、行をコメントした場合

error: cannot borrow `current_player` (via `current_player.name`) as immutable because `current_player` is also borrowed as mutable (via `current_player.next`) [E0502] 
     println!("Player to kill/save {}", current_player.name); 
             ^~~~~~~~~~~~~~~~~~~ 
note: mutable borrow occurs here (via `current_player.next`) 
     current_player = (*current_player).next.as_mut().unwrap(); 
        ^~~~~~~~~~~~~~~~~~~~~~ 
note: mutable borrow ends here 
} 

// (*current_player).killed = !(*current_player).killed; 

プログラムは正常にビルドできますが、終了時に不明なランタイムエラーが発生します。それがなぜ起こるかわからない。 ( - 一意Boxによって所有されているものとするTBox<T>と同じ)さび&mutにおいて

GAME STARTED! 
Player to kill/save A 
Player A is killed: false 
Player to kill/save B 
Player B is killed: false 
...... 
Player to kill/save C 
Player C is killed: false 
Player to kill/save A 
Player A is killed: false 
End! 
error: An unknown error occurred 

答えて

1

は変更可能ですが、ユニークなだけでなく、意味します。 unsafeで回避しようとすると、不変量に違反し、未定義の動作につながります。あなたが得るエラーはそのためです(私の推測では、あなたは二重自由(再帰的?)を引き起こしているでしょう)。 unsafe(推奨されません)と一緒に滞在したい場合は、*mutポインターをどこでも使用してください。

内部の変更可能性はあなたがしたいことです。 cellモジュールに慣れている必要があります。 This blogpost about interior mutabilityも読む価値があります。

use std::cell::{Cell,RefCell}; 
use std::rc::Weak; 

#[derive(Debug, Clone)] 
struct Player { 
    name: String, 
    killed: Cell<bool>, 
    next: RefCell<Option<Weak<Player>>>, 
} 

をしてRc(参照カウントポインタ)の後ろに、すべてのプレーヤーを保つ:

だから、私はそのようなあなたの構造体を再定義したいです。すべてのプレイヤーが主な機能のスタックにのみ住むことを計画している場合は、

next: Cell<Option<&Player>>, 

で十分です。

もう1つの選択肢は、プレーヤー全体をRc<RefCell<Player>>にすることですが、セルに変更可能な部分だけを置くことをお勧めします。

6

まず、Learning Rust With Entirely Too Many Linked Listsとお読みください。単一リンクされたリストは、多くのプログラミング言語がそれらを扱うのとは異なり、単純ではありません。サーキュラーリンクリスト(または二重リンクリスト)は、オーナーシップ、コアラストコンセプトになるとかなり複雑です。

循環リンクリストをお持ちの場合は、各アイテムを所有していますか?これは、値の所有者が値を削除することが予想されるため、知っておくことが重要です。

同様に、複数の変更可能な参照は、の理由でが許可されていません。必要に応じて、RefCellのようなコードの構造に直接対応しない変更可能なタイプがあります。

クラッシュの理由はここにあります:unsafe。あなたはコンパイラに "これはクールです、私がやっていることを知っています"と言ってきました。そして、あなたはあなたが維持しようとしているすべての保証を破ります。 unsafeを使用する場合は、The Rustonomicon: The Dark Arts of Advanced and Unsafe Rust Programmingとお読みください。

どれも本当に必要です。 Vecを使用してください:

#[derive(Debug, Clone)] 
struct Player { 
    name: String, 
    killed: bool, 
} 

fn main() { 
    let mut players = vec![ 
     Player { 
      name: "C".to_string(), 
      killed: false, 
     }, 
     Player { 
      name: "B".to_string(), 
      killed: false, 
     }, 
     Player { 
      name: "A".to_string(), 
      killed: false, 
     }, 
    ]; 

    println!("GAME STARTED!"); 

    let mut current_player_idx = 0; 
    let player_count = players.len(); 

    for _ in 0..10 { 
     let current_player = &mut players[current_player_idx]; 

     println!("Player to kill/save {}", current_player.name); 
     current_player.killed = !current_player.killed; 
     println!(
      "Player {} is killed: {}", 
      current_player.name, current_player.killed 
     ); 

     current_player_idx += 1; 
     current_player_idx %= player_count; 
    } 
    println!("End!"); 
} 

明示的逆参照は必要ありません。

+1

私は、 '' Iterator :: cycle'](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.cycle)を 'のVec'で使うことができます。手動でインデックスをインクリメントしてラッピングするのではなく、 –

+0

@ChrisEmersonそれは素晴らしい考えです!残念ながら、 'itterator :: cycle'を可変イテレータで使用することはできません:' std :: slice :: IterMut <...>:std :: clone :: Clone is not'。そうしないと、複数の可変参照を同時に取得することができます。ベクトルを 'RefCell 'に変更し、 'borrow_mut'を実行することができます。 – Shepmaster

+1

@Shepmaster @Chris 'cycle'を' kill'に 'Cell 'に変更するもう一つの方法です。これは 'RefCell'よりも簡単です。 – krdln

関連する問題