Rust: لا ينبغي أن تكون نطاقات الاستعارة معجمية دائمًا

تم إنشاؤها على ١٠ مايو ٢٠١٣  ·  44تعليقات  ·  مصدر: rust-lang/rust

إذا اقترضت بشكل غير قابل للتغيير في اختبار if ، فإن الاقتراض يستمر لكامل تعبير if . هذا يعني أن الاقتراضات القابلة للتغيير في البنود ستؤدي إلى فشل مدقق الاستعارة.

يمكن أن يحدث هذا أيضًا عند الاقتراض في تعبير المطابقة ، والحاجة إلى استعارة قابلة للتغيير في أحد الأشكال.

انظر هنا للحصول على مثال لمربعات if borrows ، والتي تؤدي إلى تجميد أقرب @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 = & ... سيكون جزء من التحليل عبارة عن قاعدة تشير إلى الوقت الذي يمكن فيه اعتبار القيمة المرتجعة التي تحتوي على مؤشر مستعار لم تسقط بعد. سيغطي هذا حالات مثل "match table.find (...) {... None => {تتوقع-table-not-to-be-قرض-هنا؛}}"

الجزء الأكثر إثارة للاهتمام في كل هذا هو قواعد الهروب بالطبع. أعتقد أن القواعد يجب أن تأخذ في الاعتبار التعريف الرسمي للوظيفة ، وعلى وجه الخصوص للاستفادة من المعرفة التي تمنحنا إياها مدى الحياة. على سبيل المثال ، قد تعتبر معظم تحليلات الهروب المؤشر p مهربًا إذا رأوا مكالمة مثل foo(p) . لكن ليس بالضرورة أن نفعل ذلك. إذا تم الإعلان عن الوظيفة على أنها:

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

ثم في الواقع نحن نعلم أن foo لا يحتفظ بـ p لفترة أطول من العمر a . ومع ذلك ، يجب اعتبار وظيفة مثل 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 هروب، p يجب أن تعتبر نجا حتى عمر q تنتهي. لكنني أعتقد أن هذا يختلف - إلى حد ما - بشكل طبيعي عن الطريقة التي نتعامل بها اليوم: أي أن المترجم يلاحظ أن q اقترض p (في البداية) مدى الحياة "q" (أي ، من المتغير نفسه) وإذا حدث هروب q ، فسيتم ترقيته إلى العمر المعجمي الكامل. أعتقد أن الجزء الصعب هو في تدفق البيانات ، مع معرفة مكان إدراج عمليات القتل - لا يمكننا إدراج القتل مقابل p الفور عندما يموت p إذا تمت إعادة اقتراضه. حسنًا ، لن أضيع المزيد من الوقت في هذا الأمر ، ويبدو أنه ممكن ، وفي أسوأ الأحوال توجد حلول أبسط تكون مناسبة للمواقف الشائعة (على سبيل المثال ، ضع في اعتبارك أن p قد هرب طوال العمر من q ، بغض النظر عما إذا كان القرض q هربًا أم لا).

على أي حال ، هناك ما يبرر المزيد من التفكير ، لكنني بدأت أرى كيف يمكن أن يعمل هذا. ما زلت مترددًا في الشروع في أي امتدادات مثل هذه حتى يتم إصلاح # 2202 و # 8624 ، وهما المشكلتان المعروفتان في BOARDC. أود أيضًا أن أحقق مزيدًا من التقدم في إثبات الصحة قبل أن نبدأ في توسيع النظام. الامتداد الآخر الموجود على الخط الزمني هو # 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 نفد بمجرد أن ننتهي من تقييم المسند ، ولكن بالطبع ، يمتد الاقتراض للمباراة بأكملها في الوقت الحالي.

كان لدي المزيد من الأفكار حول كيفية ترميز هذا. خطتي الأساسية هي أنه لكل قرض سيكون هناك قطعتان: نسخة هاربة ونسخة غير هاربة. في البداية نضيف الإصدار غير المهرب. عندما يهرب المرجع ، نضيف البتات التي تم تجاوزها. عندما يموت متغير (أو مؤقت ، إلخ) ، فإننا نقتل البتات غير الهاربة - لكننا نترك البتات الهاربة (إذا تم ضبطها) كما هي. أعتقد أن هذا يغطي جميع الأمثلة الرئيسية.

سالب جاد

هل يغطي هذا العدد هذا؟

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
}

نسخة لي

أمثلة جيدة في # 9113

نسخة لي

قد أكون مخطئًا ، ولكن يبدو أن الكود التالي يصيب هذا الخطأ أيضًا:

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 مرتين ، لأن الواجهة Buffer لا تضمن إرجاع البيانات التي تحققت من صحتها في المرة الثانية. إذا جربت هذا ، فإن الكود غير صحيح من الناحية الفنية ولكن مدقق النوع يمرره بسعادة.
  2. لا يمكنني فعل الكثير بشأن top_up ، لأنه جزء شرير من التعليمات البرمجية يحتاج إلى تغيير كل شيء بطرق معقدة.
  3. لا يمكنني نقل الرابط المخالف + اختبار + العودة إلى وظيفة أخرى ، لأن واجهة برمجة التطبيقات الجديدة ستظل تواجه جميع المشكلات نفسها (ما لم يسمح لي if let باختبار _ ثم الربط؟).

يبدو الأمر كما لو أن القيد 'a الناحية المثالية لا ينبغي نشره بالكامل إلى read . لكنني فوق رأسي هنا. سأحاول if let التالي.

حسنًا ، if let لم يدخل البناء الليلة الماضية ، ولكن نظرًا لأنه من المفترض أنه مجرد إعادة كتابة AST ، أعتقد أنه من المحتمل أن يفشل بنفس الطريقة التي فشل بها 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 ) ، وأطبق على الفور عمرًا جديدًا على أساس self ، الذي يمتلك self.input . من الناحية المثالية ، أريد أن يكون لدى read عمر معجمي يساوي Block A ، ولا أريد أن يتم رفعه إلى مستوى الكتلة _lexical_ لمجرد أنني مررته إلى return . من الواضح أن المدقق مدى الحياة لا يزال بحاجة إلى إثبات أن النتيجة تتوافق مع مدى الحياة مع 'a ، لكنني لا أفهم لماذا يعني ذلك أن LIFETIME ( read ) تحتاج إلى توحيد مع LIFETIME ( 'a ).

من الممكن تمامًا أن أكون في حيرة من أمري ، أو أن الكود الخاص بي غير آمن بشكل مروّع. :-) لكن يبدو أن هذا يجب أن يعمل ، فقط إذا كان بإمكاني الاتصال بـ return self.input.fill_buf() دون أي مشكلة على الإطلاق. هل هناك أي طريقة لإضفاء الطابع الرسمي على هذا الحدس؟

emk إذن هذا هو "الكود الثابت" الذي لا تقوم مناطق SEME (أي المناطق غير المعجمية) بإصلاحها ، على الأقل ليس من تلقاء نفسها. لدي بعض الأفكار حول كيفية إصلاحه بشكل جيد في المترجم ، لكنه امتداد غير تافه لمناطق SEME. عادة ما تكون هناك طريقة لحل هذا عن طريق إعادة هيكلة الكود. اسمحوا لي أن أرى ما إذا كان بإمكاني اللعب بها وتقديم مثال جيد.

أود أن أعرف ما إذا كان يتم إعادة النظر في هذا الإصدار 1.0. لقد كان هذا يحدث كثيرًا مؤخرًا ، وأخشى أن هذا سوف يتحول من ورقة إلى جرح في اللحم بمجرد أن يجلب 1.0 اندفاع الانتباه. نظرًا لأن ميزة Rust الأكثر وضوحًا وتحدثًا عنها ، من المهم جدًا أن يكون الاقتراض مصقولًا وقابلًا للاستخدام.

هل هناك إطار زمني على RFC لهذا؟

nikomatsakis لدي مثال بسيط من العالم الواقعي خرج من العمل على Entry API ، إذا كان يساعد:

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 = & ... سأجد أمرًا مقبولًا في الوقت الحالي للإعلان عن نهاية نطاق هذا الاستعارة يدويًا. يمكن للإصدارات اللاحقة ببساطة تجاهل هذه التعليمات إذا لم تكن هناك حاجة إليها أو وضع علامة عليها كخطأ إذا تم اكتشاف إعادة استخدام 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);
}

هناك طريقة الإسقاط () في المقدمة التي تقوم بذلك. لكن لا يبدو
للمساعدة في الاقتراضات المتغيرة.

في الأحد ، 5 أبريل 2015 ، الساعة 1:41 مساءً ، كتب axeoth [email protected] :

/ * (مطلوب) _ // _ fn main () {let mut x = 10 ؛ دع y = & mut x؛ println! ("x = {}، y = {}"، x، _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.

metajack الرابط الخاص

بعد بعض البحث ، أعتقد أن هذا يعادل الكود الأصلي:
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

لست متأكدًا من مدى صلة هذه الأمثلة المحددة الآن بما أنها كانت قبل Rust 1.0.

metajack سيكون رائعًا أن يكون لديك مثال بسيط للغاية (post 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 في الاستعارة.

من الغريب أنك إذا لم تحاول التقاط int في ذراع Some (على سبيل المثال ، استخدم Some(_) ) ، فسيتم تجميعها.

wyverland أوه يا ، لقد ضربت ذلك بالأمس فقط ، مزعج جدًا.

metajack هل يمكنك تعديل

لم يتم ضربها بعد كل ليلة ، لكني أريد فقط أن أقول إن هذا يجمع الآن:

#![feature(nll)]

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

    match vec.first() {
        None => vec.push(5),
        Some(v) => unreachable!(),
    }
}
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات