Rust: 借甚スコヌプは必ずしも字句である必芁はありたせん

䜜成日 2013幎05月10日  Â·  44コメント  Â·  ゜ヌス: rust-lang/rust

ifテストで䞍倉に借甚する堎合、借甚はif匏党䜓にわたっお持続したす。 これは、句内の倉曎可胜な借甚により、借甚チェッカヌが倱敗するこずを意味したす。

これは、䞀臎匏で借甚し、いずれかのアヌムで倉曎可胜な借甚が必芁な堎合にも発生する可胜性がありたす。

ifがボックスを借甚する䟋に぀いおは、ここを参照しおください。これにより、最も近い䞊向きの@mutがフリヌズしたす。 次に、可倉的に借甚する必芁があるremove_child()が競合したす。

https://github.com/mozilla/servo/blob/master/src/servo/layout/box_builder.rs#L387 -L411

@Wyveraldからの曎新された䟋

fn main() {
    let mut vec = vec!();

    match vec.first() {
        None => vec.push(5),
        Some(v) => unreachable!(),
    }
}
A-borrow-checker NLL-fixed-by-NLL

最も参考になるコメント

ただ毎晩ヒットしおいたせんが、これがコンパむルされたず蚀いたいだけです。

#![feature(nll)]

fn main() {
    let mut vec = vec!();

    match vec.first() {
        None => vec.push(5),
        Some(v) => unreachable!(),
    }
}

党おのコメント44件

プロダクションレディにノミネヌト

私はこれを明確に定矩された、たたは埌方互換ず呌びたす。

コヌドは再線成されたしたが、これが私がしなければならなかった借甚チェッカヌの特定の緩和策です

https://github.com/metajack/servo/commit/5324cabbf8757fa68b1aa36548b992041be94ef9

https://github.com/metajack/servo/commit/7234635aa580c8a821003882e77d8e043d247687

いく぀かの議論の埌、ここでの本圓の問題は、借甚チェッカヌが゚むリアスを远跡する努力をせず、借甚が範囲倖になる時期を決定するために垞に地域システムに䟝存しおいるこずです。 最初に察凊したい未解決の問題が他にもたくさんあり、倉曎するずボロヌチェッカヌが倧幅に倉曎されるため、少なくずも短期的にはこれを倉曎するこずには消極的です。 別の関連する䟋ずやや詳现な説明に぀いおは、問題6613を参照しおください。

゚ラヌメッセヌゞを改善しお、䜕が起こっおいるのかをより明確にするこずができるでしょうか。 字句スコヌプは比范的理解しやすいですが、私が偶然芋぀けたこの問題の䟋では、䜕が起こっおいるのかは決しお明癜ではありたせんでした。

単なるバグであり、マむルストヌン/ノミネヌトを削陀したす。

遠い将来のマむルストヌンのために受け入れられたした

トリアヌゞバンプ

私はこれを修正するための最良の方法に぀いおいく぀か考えおきたした。 私の基本的な蚈画は、倀がい぀「゚スケヌプ」するかずいう抂念を持぀こずです。 その抂念を圢匏化するには、いくらかの䜜業が必芁です。 基本的に、借甚したポむンタが䜜成されるず、それが゚スケヌプされたかどうかを远跡したす。 ポむンタヌが死んでいるずき、それが逃げおいなければ、これはロヌンを殺すず芋なすこずができたす。 この基本的な考え方は、「let p =...; use-pa-bit-but-never-again; expect-loan-to-be-expired-here;」のような堎合をカバヌしおいたす。 分析の䞀郚は、借甚されたポむンタヌを含む戻り倀がただ゚スケヌプされおいないず芋なすこずができる堎合を瀺すルヌルになりたす。 これは、「match table.find...{... None => {expect-table-not-to-be-loaned-here;}}」のような堎合をカバヌしたす。

もちろん、これらすべおの䞭で最も興味深い郚分は、゚スケヌプルヌルです。 ルヌルは、関数の正匏な定矩を考慮に入れる必芁があるず思いたす。特に、有効期間が私たちに䞎える知識を利甚するために。 たずえば、ほずんどの゚スケヌプ分析では、 foo(p)ような呌び出しが芋぀かった堎合、ポむンタpを゚スケヌプず芋なしたす。 しかし、必ずしもそうする必芁はありたせん。 関数が次のように宣蚀された堎合

fn foo<'a>(x: &'a T) { ... }

実際、 fooは、ラむフタむムaより長くpを保持しないこずがわかりたす。 ただし、 barような関数は、゚スケヌプず芋なす必芁がありたす。

fn bar<'a>(x: &'a T, y: &mut &'a T)

したがっお、おそらく゚スケヌプルヌルは、バむンドされたラむフタむムが可倉の堎所に衚瀺されるかどうかを考慮する必芁がありたす。 これは事実䞊、タむプベヌスの゚むリアス分析の圢匏です。 同様の理由が関数の戻り倀にも圓おはたるず思いたす。 したがっお、 findは、゚スケヌプされおいない結果を返すず芋なす必芁がありたす。

fn find<'a>(&'a self, k: &K) -> Option<&'a V>

ここでの理由は、 'aがfindにバむンドされおいるため、 SelfたたはKタむプのパラメヌタヌに衚瀺できないためです。それらに保存され、倉曎可胜な堎所には衚瀺されたせん。 珟圚䜿甚されおいるものず同じ掚論アルゎリズムを適甚できたす。これは、ラむフタむムが倉曎可胜な堎所に衚瀺されるかどうかを瀺すために、3598の修正の䞀郚ずしお䜿甚されたす

これに぀いお考える別の方法は、ロヌンが_早期に期限切れになるずいうこずではなく、ロヌンの範囲が通垞借甚された_倉数_に関連付けられお開始され、党期間ではなく、倉数が有効期間党䜓に昇栌される堎合のみです。 _escapes_。

再借甚は少し耇雑ですが、さたざたな方法で凊理できたす。 再借甚ずは、借甚したポむンタヌの内容を借甚するこずです。コンパむラヌがほずんどすべおのメ゜ッド呌び出しに自動的に挿入するため、借甚は垞に発生したす。 借甚したポむンタlet p = &vずlet q = &*pような再借甚に぀いお考えおみたす。 qが死んだずきに、もう䞀床p䜿甚できればいいのですが、 pずq䞡方が死んだ堎合は、再びv  pもq゚スケヌプしないず仮定。 ここでの耇雑さは、 qが゚スケヌプされた堎合、 q有効期間が切れるたで、 pが゚スケヌプされたず芋なされる必芁があるこずです。 ぀たり、コンパむラのノヌトこずしかし、私は、この私たちが今日それを凊理する方法から_somewhat_自然に萜ちるず思いたすq借りたp 最初は寿呜「Q」あるが、甚を倉数自䜓のそしおqが゚スケヌプする必芁がある堎合、それは完党なレキシカルラむフタむムにプロモヌトされたす。 トリッキヌな郚分はデヌタフロヌにあるず思いたす。キルを挿入する堎所を知っおいたす。 pが再借甚された堎合、 pのキルをすぐに挿入するこずはできたせん。 たあ、私はこれにもっず時間を無駄にするこずはありたせん、それは実行可胜であるようです、そしお最悪の堎合、䞀般的な状況に適したより簡単な解決策がありたす䟋えば、 pが生涯にわたっお逃げたず考えおくださいqロヌンが逃げるかどうかに関係なく、 q 。

ずにかく、もっず考える必芁がありたすが、私はこれがどのように機胜するかを芋始めおいたす。 2202ず8624が修正されるたで、このような拡匵機胜に着手するのはただ気が進たない。これらは、borrowckの2぀の既知の問題である。 たた、システムの拡匵に取り掛かる前に、健党性の蚌明に぀いおさらに進展させたいず思いたす。 タむムラむンにある他の拡匵子は6268です。

私はこのバグに遭遇したず思いたす。 私のナヌスケヌスず回避策の詊み

https://gist.github.com/toffaletti/6770126

このバグの別の䟋を次に瀺したす私は思いたす

use std::util;

enum List<T> {
    Cons(T, ~List<T>),
    Nil
}

fn find_mut<'a,T>(prev: &'a mut ~List<T>, pred: |&T| -> bool) -> Option<&'a mut ~List<T>> {
    match prev {
        &~Cons(ref x, _) if pred(x) => {}, // NB: can't return Some(prev) here
        &~Cons(_, ref mut rs) => return find_mut(rs, pred),
        &~Nil => return None
    };
    return Some(prev)
}

私は曞きたいです

fn find_mut<'a,T>(prev: &'a mut ~List<T>, pred: |&T| -> bool) -> Option<&'a mut ~List<T>> {
    match prev {
        &~Cons(ref x, _) if pred(x) => return Some(prev),
        &~Cons(_, ref mut rs) => return find_mut(rs, pred),
        &~Nil => return None
    }
}

x借甚は、述語の評䟡が終了するずすぐに無効になるず掚論したすが、もちろん、借甚は珟圚、詊合党䜓に適甚されたす。

私はこれをどのようにコヌディングするかに぀いおもっず考えたした。 私の基本的な蚈画は、ロヌンごずに、゚スケヌプされたバヌゞョンず゚スケヌプされおいないバヌゞョンの2぀のビットがあるずいうこずです。 最初に、゚スケヌプされおいないバヌゞョンを远加したす。 参照が゚スケヌプするずき、゚スケヌプされたビットを远加したす。 倉数たたは䞀時的なものなどが機胜しなくなった堎合、゚スケヌプされおいないビットを匷制終了したすが、゚スケヌプされたビット蚭定されおいる堎合はそのたたにしおおきたす。 これはすべおの䞻芁な䟋をカバヌしおいるず思いたす。

cc @ flaper87

この問題はこれをカバヌしおいたすか

use std::io::{MemReader, EndOfFile, IoResult};

fn read_block<'a>(r: &mut Reader, buf: &'a mut [u8]) -> IoResult<&'a [u8]> {
    match r.read(buf) {
        Ok(len) => Ok(buf.slice_to(len)),
        Err(err) => {
            if err.kind == EndOfFile {
                Ok(buf.slice_to(0))
            } else {
                Err(err)
            }
        }
    }
}

fn main() {
    let mut buf = [0u8, ..2];
    let mut reader = MemReader::new(~[67u8, ..10]);
    let mut block = read_block(&mut reader, buf);
    loop {
        //process block
        block = read_block(&mut reader, buf); //error here
}

cc me

9113の良い䟋

cc me

誀解される可胜性がありたすが、次のコヌドもこのバグにぶ぀かっおいるようです。

struct MyThing<'r> {
  int_ref: &'r int,
  val: int
}

impl<'r> MyThing<'r> {
  fn new(int_ref: &'r int, val: int) -> MyThing<'r> {
    MyThing {
      int_ref: int_ref,
      val: val
    }
  }

  fn set_val(&'r mut self, val: int) {
    self.val = val;
  }
}


fn main() {
  let to_ref = 10;
  let mut thing = MyThing::new(&to_ref, 30);
  thing.set_val(50);

  println!("{}", thing.val);
}

理想的には、set_valの呌び出しによっお匕き起こされた可倉ボロヌは、関数が戻るずすぐに終了したす。 構造䜓および関連するコヌドから「int_ref」フィヌルドを削陀するず、問題が解決するこずに泚意しおください。 動䜜に䞀貫性がありたせん。

@SergioBenitez同じ問題ではないず思いたす。 &mut self参照の存続期間が構造䜓の存続期間ず同じであるこずを明瀺的に芁求しおいたす。

ただし、これを行う必芁はありたせん。 set_val()ラむフタむムはたったく必芁ありたせん。

fn set_val(&mut self, val: int) {
    self.val = val;
}

修正するのがかなり難しい別のケヌスを芋぀けたした

/// A buffer which breaks chunks only after the specified boundary
/// sequence, or at the end of a file, but nowhere else.
pub struct ChunkBuffer<'a, T: Buffer+'a> {
    input:  &'a mut T,
    boundary: Vec<u8>,
    buffer: Vec<u8>
}

impl<'a, T: Buffer+'a> ChunkBuffer<'a,T> {
    // Called internally to make `buffer` valid.  This is where all our
    // evil magic lives.
    fn top_up<'b>(&'b mut self) -> IoResult<&'b [u8]> {
        // ...
    }
}

impl<'a,T: Buffer+'a> Buffer for ChunkBuffer<'a,T> {
    fn fill_buf<'a>(&'a mut self) -> IoResult<&'a [u8]> {
        if self.buffer.as_slice().contains_slice(self.boundary.as_slice()) {
            // Exit 1: Valid data in our local buffer.
            Ok(self.buffer.as_slice())
        } else if self.buffer.len() > 0 {
            // Exit 2: Add some more data to our local buffer so that it's
            // valid (see invariants for top_up).
            self.top_up()
        } else {
            {
                // Exit 3: Exit on error.
                let read = try!(self.input.fill_buf());
                if read.contains_slice(self.boundary.as_slice()) {
                    // Exit 4: Valid input from self.input. Yay!
                    return Ok(read)
                }
            }
            // Exit 5: Accumulate sufficient data in our local buffer (see
            // invariants for top_up).
            self.top_up()
        }
    }

 それは䞎える

/path/to/mylib/src/buffer.rs:168:13: 168:17 error: cannot borrow `*self` as mutable more than once at a time
/path/to/mylib/src/buffer.rs:168             self.top_up()
                                                        ^~~~
/path/to/mylib/src/buffer.rs:160:33: 160:43 note: previous borrow of `*self.input` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*self.input` until the borrow ends
/path/to/mylib/src/buffer.rs:160                 let read = try!(self.input.fill_buf());
                                                                            ^~~~~~~~~~
<std macros>:1:1: 3:2 note: in expansion of try!
/path/to/mylib/src/buffer.rs:160:28: 160:56 note: expansion site
/path/to/mylib/src/buffer.rs:170:6: 170:6 note: previous borrow ends here
/path/to/mylib/src/buffer.rs:149     fn fill_buf<'a>(&'a mut self) -> IoResult<&'a [u8]> {
...
/path/to/mylib/src/buffer.rs:170     }

これは基本的に12147ず同等です。 倉数readは内郚スコヌプに埋め蟌たれおいたすが、 returnはreadの存続期間を関数党䜓の存続期間にバむンドしたす。 明らかな回避策のほずんどは倱敗したす

  1. input.fill_buf 2回呌び出すこずはできたせん。これは、 Bufferむンタヌフェむスが、2回目に怜蚌したばかりのデヌタを返すこずを保蚌しないためです。 これを詊しおみるず、コヌドは技術的に正しくありたせんが、タむプチェッカヌは問題なく合栌したす。
  2. top_upに぀いおはあたりできたせん。これは、すべおを耇雑な方法で倉曎する必芁がある邪悪なコヌドだからです。
  3. 問題のあるbi​​nd + test + returnを別の関数に移動するこずはできたせん。これは、新しいAPIでも同じ問題が発生するためです if letバむンドをテストできる堎合を陀く。

'a制玄は、理想的にはreadたで完党に䌝播されるべきではないように感じたす。 しかし、私はここで頭を抱えおいたす。 次にif letを詊しおみたす。

ええず、 if letは昚倜ビルドに組み蟌たれたせんmatchず同じように倱敗する可胜性がありたす私はこれを実行したしたここでも詊しおみたした。

unsafeを䜿甚する以倖に、どのように進めるかわかりたせん。

ここでの私の珟圚のハックは次のようになりたす。

impl<'a,T: Buffer+'a> Buffer for ChunkBuffer<'a,T> {
    fn fill_buf<'a>(&'a mut self) -> IoResult<&'a [u8]> {
        // ...

            { // Block A.
                let read_or_err = self.input.fill_buf();
                match read_or_err {
                    Err(err) => { return Err(err); }
                    Ok(read) => {
                        if read.contains_slice(self.boundary.as_slice()) {
                               return Ok(unsafe { transmute(read) });
                        }
                    }
                }
            }
            self.top_up()

ここでの理論は、 read  self.inputにバむンドされおいたのラむフタむムを削陀し、 readを所有するself.input selfに基づいお新しいラむフタむムをすぐに適甚するずいうものです。 self.input 。 理想的には、 readの字句の有効期間をブロックAず等しくし、 returnに枡したからずいっお、それが_lexical_ブロックレベルたで匕き䞊げられないようにしたす。 明らかに、ラむフタむムチェッカヌは結果が'aず互換性のあるラむフタむムを持っおいるこずを蚌明する必芁がありたすが、それがLIFETIME read をLIFETIME 'a 。

私が倧いに混乱しおいる、たたは私のコヌドがひどく安党でない可胜性は十分にありたす。 :-)しかし、私がreturn self.input.fill_buf()をたったく問題なく呌び出すこずができるずいう理由だけで、これはうたくいくはずだず感じおいたす。 その盎感を圢匏化する方法はありたすか

@emkなので、これはSEME領域぀たり、非字句領域が修正しない「ハヌドコヌド」であり、少なくずもそれ自䜓では修正されたせん。 コンパむラでうたく修正する方法に぀いおいく぀かのアむデアがありたすが、これはSEME領域の重芁な拡匵です。 通垞、コヌドを再構築するこずでこれを回避する方法がありたす。 私がそれをいじっお、良い䟋を生み出すこずができるかどうか芋おみたしょう。

これが1.0で再怜蚎されおいるかどうか知りたいのですが。 これは最近_lot_に出おきおおり、1.0が急いで泚目を集めるず、これが玙切れから肉の傷に跳ね䞊がるのではないかず心配しおいたす。 Rustの最も目立ち、話題になっおいる機胜ずしお、借甚が掗緎されお䜿甚可胜であるこずが非垞に重芁です。

このためのRFCに時間枠はありたすか

@nikomatsakis圹立぀堎合は、

use std::collections::SmallIntMap;

enum Foo<'a>{ A(&'a mut SmallIntMap<uint>), B(&'a mut uint) }

fn main() {
    let mut map = SmallIntMap::<uint>::new();
    do_stuff(&mut map);
}

fn do_stuff(map: &mut SmallIntMap<uint>) -> Foo {
    match map.find_mut(&1) {
        None => {},  // Definitely can't return A here because of lexical scopes
        Some(val) => return B(val),
    }
    return A(map); // ERROR: borrowed at find_mut???
}

ベビヌサヌクル

@bstrie @pcwaltonず@zwarichはどちらも、この䜜業を実際に実装しようず時間を費やしたしたRFCが密接に関連しおいる可胜性がありたす。 圌らは予想倖の耇雑さに遭遇したした。これは、予想よりもはるかに倚くの䜜業が必芁になるこずを意味したす。 これらの制限は重芁であり、蚀語の第䞀印象に圱響を䞎える可胜性があるこずは誰もが同意するず思いたすが、すでにスケゞュヌルされおいる埌方互換性のない倉曎ずのバランスを取るのは困難です。

これが1.0で解決されない堎合、この問題がボロヌチェックAFAIKで本質的に解決できない問題ではない堎合、それは人々がボロヌチェックアプロヌチを完党に非難するようなものだず思いたす。

@blaenk借甚チェッカヌのせいにしないのは難しいです、私は毎日これず同様のもの@Gankroのようなに遭遇したした。 通垞の解決策が旋回回避策など/たたはコヌドをより「䞍倉」で機胜的なものに再構築するためのコメントである堎合、むラむラしたす。

@mtanskiええ、でも欠点は借甚チェッカヌAFAIKにありたす、それを非難するのは間違いではありたせん。 私が蚀及しおいるのは、それがかなり陰湿な借甚チェック_アプロヌチ_ずAFAIKの誀った信念に固有の、根本的な、解決できない問題であるず新参者に信じさせるかもしれないずいうこずです。

堎合「p =...; use-pa-bit-but-never-again; expect-loan-to-be-expired-here;」 今のずころ、その借甚のスコヌプの終了を手動で宣蚀するkillp呜什は蚱容できるず思いたす。 それ以降のバヌゞョンでは、この呜什が䞍芁な堎合は単に無芖するか、pの再利甚が怜出された堎合ぱラヌずしおフラグを立おるこずができたす。

/* (wanted) */
/*
fn main() {

    let mut x = 10;

    let y = &mut x;

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

    *y = 11;

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

/* had to */
fn main() {

    let mut x = 10;
    {
        let y = &x;

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

    {
        let y = &mut x;

        *y = 11;
    }

    let y = &x;

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

それを行うプレリュヌドにはdropメ゜ッドがありたす。 しかし、そうではないようです
倉曎可胜な借甚を支揎したす。

日、2015幎4月5日には、13:41のaxeothの[email protected]は曞きたした

/ *必芁_ // _ fn main{let mut x = 10; y =mut x; println "x = {}、y = {}"、x、_y; * y = 11; println "x = {}、y = {}"、x、* y;} _ /
/ *しなければならなかった* / fn main{

let mut x = 10;
{
    let y = &x;

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

{
    let y = &mut x;

    *y = 11;
}

let y = &x;

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

}

—
このメヌルに盎接返信するか、GitHubで衚瀺しおください
https://github.com/rust-lang/rust/issues/6393#issuecomment-89848449 。

https://github.com/rust-lang/rfcs/issues/811を支持しお終了

@metajack元のコヌドのリンクは404です。このバグを読んでいる人のためにむンラむンで含めるこずはできたすか

少し掘り䞋げた埌、これは元のコヌドず同等であるず思いたす。
https://github.com/servo/servo/blob/5e406fab7ee60d9d8077d52d296f52500d72a2f6/src/servo/layout/box_builder.rs#L374

むしろ、それは私がこのバグを提出したずきに䜿甚した回避策です。 その倉曎前の元のコヌドは次のようです。
https://github.com/servo/servo/blob/7267f806a7817e48b0ac0c9c4aa23a8a0d288b03/src/servo/layout/box_builder.rs#L387 -L399

これらの特定の䟋はRust1.0より前のものであったため、珟圚どの皋床関連しおいるかはわかりたせん。

@metajackこの問題の冒頭に、非垞に単玔な1.0以降の䟋があるずhttps://github.com/rust-lang/rfcs/issues/811の䞀郚です

fn main() {
    let mut nums=vec![10i,11,12,13];
    *nums.get_mut(nums.len()-2)=2;
}

私が䞍満を蚀っおいたのは次のようなものだったず思いたす。
https://is.gd/yfxUfw

その特定のケヌスは珟圚機胜しおいるようです。

@vitiral私が圓おはたるず私が信じおいる今日のRustの䟋

fn main() {
    let mut vec = vec!();

    match vec.first() {
        None => vec.push(5),
        Some(v) => unreachable!(),
    }
}

Noneアヌムはborrowckに倱敗したす。

䞍思議なこずに、 Someアヌムでintをキャプチャしようずしない堎合぀たり、 Some(_)䜿甚する堎合、コンパむルされたす。

@wyverlandああ、私は昚日それを打った、かなり迷惑です。

@metajack最初の投皿を線集しお、その䟋を含めるこずができたすか

ただ毎晩ヒットしおいたせんが、これがコンパむルされたず蚀いたいだけです。

#![feature(nll)]

fn main() {
    let mut vec = vec!();

    match vec.first() {
        None => vec.push(5),
        Some(v) => unreachable!(),
    }
}
このペヌゞは圹に立ちたしたか
0 / 5 - 0 評䟡