Rust: مشكلة في التعقب لـ Pin APIs (RFC 2349)

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

مشكلة تتبع Rust-lang / rfcs # 2349

منع الاستقرار:

  • [x] التنفيذ (PR # 49058)
  • [ ] توثيق

أسئلة لم يتم حلها:

  • [] هل يجب أن نقدم ضمانات أقوى بشأن تسريب بيانات !Unpin ؟

تحرير : تعليق موجز: https://github.com/rust-lang/rust/issues/49150#issuecomment -409939585 (في الجزء المخفي افتراضيًا)

B-RFC-approved C-tracking-issue T-lang T-libs

التعليق الأكثر فائدة

rfcbot قلق api-refactor

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

ال 211 كومينتر

ألاحظ الآن أن تثبيت المكدس ليس جزءًا من RFC ، withoutboats هل تخطط لإطلاق صندوق لهذا ، أم هل يجب علي نسخ رمز المثال في الصندوق الذي يحتاجه؟

@ Nemo157 يجب عليك نسخه والإبلاغ عن تجربتك!

السؤال الذي لم يتم حله حول تسريب بيانات Unpin يتعلق بهذا. واجهة برمجة التطبيقات هذه غير سليمة إذا قلنا أنه لا يمكنك الكتابة فوق بيانات Unpin في Pin ما لم يتم تشغيل أداة التدمير ، كما طلب cramertj . هناك واجهات برمجة تطبيقات أخرى ، أقل راحة ، تعمل من أجل ذلك. من غير الواضح ما هو الخيار الصحيح هنا - هل المكدس المريح الذي يثبت API أكثر فائدة أم أن الضمان الإضافي بشأن التسرب أكثر فائدة؟

شيء واحد سألاحظه هو أن تثبيت المكدس لم يكن كافيًا لأشياء مثل Future::poll داخل الماكرو await! ، لأنه لم يسمح لنا بالاستقصاء في حلقة. سأكون مهتمًا إذا واجهت هذه المشكلات ، وكيف / إذا قمت بحلها إذا قمت بذلك.

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

فقط حاولت تجربة API. يبدو أن التعريف التالي (الذي اقترحه RFC) لـ Future لم يعد آمنًا من الكائنات؟

trait Future {
    type Item;
    type Error;

    fn poll(self: Pin<Self>, cx: &mut task::Context) -> Poll<Self::Item, Self::Error>;
}

لا يهم. تم العثور على ملاحظة حول خطة لجعل arbitrary_self_types كائنًا آمنًا.

تضمين التغريدة

شيء واحد سألاحظه هو أن تثبيت المكدس لم يكن كافيًا لأشياء مثل Future :: استطلاع داخل الانتظار! الماكرو ، لأنه لم يسمح لنا بالاقتراع في حلقة.

هل يمكن توضيح ذلك؟

RalfJung أنت بحاجة إلى Pin لدعم عمليات إعادة الاقتراض مثل Pin ، وهو ما لا يحدث حاليًا.

cramertj هذا يبدو وكأنه قيد على API Pin ، وليس لواجهة برمجة تطبيقات تثبيت المكدس؟

RalfJung نعم ، هذا صحيح. ومع ذلك ، يمكن إعادة استعارة PinBox كـ Pin ، بينما لا يمكن للنوع المثبت بالمكدس (استعارة واحدة مثل Pin تُنشئ استعارة طوال عمر نوع المكدس).

بالنظر إلى Pin ، يمكنني استعارته كـ &mut Pin ثم استخدام Pin::borrow - هذا شكل من أشكال إعادة الاقتراض. أعتبر أن هذا ليس نوع إعادة البورصة الذي تتحدث عنه؟

RalfJung No-- تم التخطيط لطرق مثل Future::poll لتأخذ self: Pin<Self> ، بدلاً من self: &mut Pin<Self> (وهو ليس نوع self صالح لأنه ليس ' t Deref<item = Self> - إنها Deref<Item = Pin<Self>> ).

قد يكون الأمر هو أننا يمكن أن نجعل هذا يعمل مع Pin::borrow الواقع. لست متأكدا.

cramertj لم أقترح الاتصال بـ poll على x: &mut Pin<Self> ؛ فكرت في x.borrow().poll() .

RalfJung أوه ، فهمت. نعم ، قد ينجح استخدام طريقة لإعادة الاقتراض يدويًا.

سأحاول وأتذكر نشر مثال لبعض الأشياء التي أقوم بها باستخدام Pin s الأسبوع المقبل ، بقدر ما أستطيع أن أقول إن إعادة الاقتراض تعمل بشكل مثالي. لدي نسخة مثبتة من السمة futures::io::AsyncRead جنبًا إلى جنب مع المحولات العاملة مثل fn read_exact<'a, 'b, R: PinRead + 'a>(read: Pin<'a, R>, buf: &'b [u8]) -> impl StableFuture + 'a + 'b وأنا قادر على العمل على هذا في StableFuture معقد نسبيًا وهو مجرد مكدس مثبت في الأعلى مستوى.

إليك المثال الكامل لما أستخدمه للقراءة:

pub trait Read {
    type Error;

    fn poll_read(
        self: Pin<Self>,
        cx: &mut task::Context,
        buf: &mut [u8],
    ) -> Poll<usize, Self::Error>;
}

pub fn read_exact<'a, 'b: 'a, R: Read + 'a>(
    mut this: Pin<'a, R>,
    buf: &'b mut [u8],
) -> impl StableFuture<Item = (), Error = Error<R::Error>>
         + Captures<'a>
         + Captures<'b> {
    async_block_pinned! {
        let mut position = 0;
        while position < buf.len() {
            let amount = await!(poll_fn(|cx| {
                Pin::borrow(&mut this).poll_read(cx, &mut buf[position..])
            }))?;
            position += amount;
            if amount == 0 {
                Err(Error::UnexpectedEof)?;
            }
        }
        Ok(())
    }
}

هذا أمر مزعج بعض الشيء حيث يتعين عليك تمرير المثيلات في كل مكان مثل Pin واستخدام Pin::borrow عندما تستدعي وظائف عليها.

#[async]
fn foo<'a, R>(source: Pin<'a, R>) -> Result<!, Error> where R: Read + 'a {
    loop {
        let mut buffer = [0; 8];
        await!(read_exact(Pin::borrow(&mut source), &mut buffer[..]));
        // do something with buffer
    }
}

لقد فكرت للتو في أنه بإمكاني impl<'a, R> Read for Pin<'a R> where R: Read + 'a لإيجاد حل بديل لضرورة تمرير القيم كـ Pin<'a, R> كل مكان ، بدلاً من ذلك يمكنني استخدام fn foo<R>(source: R) where R: Read + Unpin . لسوء الحظ ، فشل ذلك لأن Pin<'a, R>: !Unpin ، أعتقد أنه من الآمن إضافة unsafe impl<'a, T> Unpin for Pin<'a, T> {} لأن الدبوس نفسه مجرد مرجع وما زالت البيانات الموجودة خلفه مثبتة.

القلق: يبدو أننا نريد أن تقوم معظم الأنواع في libstd بتنفيذ Unpin دون قيد أو شرط ، حتى لو لم تكن معلمات النوع الخاصة بهم Pin . الأمثلة هي Vec ، VecDeque ، Box ، Cell ، RefCell ، Mutex ، RwLock ، Rc ، Arc . أتوقع أن معظم الصناديق لن تفكر في التثبيت على الإطلاق ، ومن ثم تكون أنواعها فقط هي Unpin إذا كانت جميع حقولها Unpin . هذا اختيار سليم ، لكنه يؤدي إلى واجهات ضعيفة بلا داع.

هل سيحل هذا الأمر من تلقاء نفسه إذا تأكدنا من تنفيذ Unpin لجميع أنواع مؤشرات libstd (ربما تتضمن مؤشرات خام) و UnsafeCell ؟ هل هذا شيء نريد أن نفعله؟

هل سيحل هذا الأمر من تلقاء نفسه إذا تأكدنا من تنفيذ Unpin لجميع أنواع مؤشرات libstd (ربما تتضمن مؤشرات خام) و UnsafeCell؟ هل هذا شيء نريد أن نفعله؟

نعم ، يبدو لي نفس الوضع مثل Send .

سألني سؤال جديد: متى يكون Pin و PinBox Send ؟ في الوقت الحالي ، تجعلهم آلية السمات التلقائية Send عندما يكون T هو Send . لا يوجد سبب مسبق للقيام بذلك ؛ تمامًا مثل الأنواع الموجودة في النوع المشترك لها سمة العلامة الخاصة بها لإمكانية الإرسال (تسمى Sync ) ، يمكننا إنشاء سمة علامة تقول عندما يكون Pin<T> هو Send ، على سبيل المثال PinSend . من حيث المبدأ ، من الممكن كتابة أنواع Send ولكن ليس PinSend والعكس صحيح.

RalfJung Pin هو الإرسال عندما يتم إرسال &mut T . PinBox عند إرسال Box<T> . لا أرى أي سبب يجعلهم مختلفين.

حسنًا ، تمامًا مثل بعض الأنواع Send ولكن ليس Sync ، يمكن أن يكون لديك نوع يعتمد على "بمجرد استدعاء هذه الطريقة بـ Pin<Self> ، يمكنني الاعتماد على عدم تحريكي مطلقًا إلى موضوع آخر ". على سبيل المثال ، قد يؤدي هذا إلى ظهور عقود آجلة يمكن إرسالها قبل أن تبدأ للمرة الأولى ، ولكن بعد ذلك يجب أن تظل في سلسلة واحدة (تشبه إلى حد كبير إمكانية نقلها قبل أن تبدأ ، ولكن بعد ذلك يجب أن تظل مثبتة). لست متأكدًا مما إذا كان بإمكاني الخروج بمثال مقنع ، ربما شيئًا ما عن المستقبل الذي يستخدم التخزين المحلي للخيط؟

لقد واجهت للتو Pin<Option<T>> -> Option<Pin<T>> يجب أن يكون عملية آمنة ولكن لا يبدو أنه من الممكن تنفيذها حتى باستخدام واجهات برمجة التطبيقات غير الآمنة الحالية ، ناهيك عن نوع واجهة برمجة التطبيقات المطلوبة لجعل هذا الرمز الآمن:

trait OptionAsPin<T> {
    fn as_pin<'a>(self: Pin<'a, Self>) -> Option<Pin<'a, T>>;
}

impl<T> OptionAsPin<T> for Option<T> {
    fn as_pin<'a>(self: Pin<'a, Self>) -> Option<Pin<'a, T>> {
        match *unsafe { Pin::get_mut(&mut self) } {
            Some(ref mut item) => Some(unsafe { Pin::new_unchecked(item) }),
            None => None,
        }
    }
}

(من الممكن حل بديل باستخدام التحويل لإجبار الحياة ، لكن هذا يجعلني أشعر بالرعب الشديد).

أرغب في إضافة سؤال لم يتم حله: هل يجب إعادة نوع مرجع مشترك مثبت؟ أعتقد أن الجواب نعم. انظر هذا المنشور مع المناقشة لمزيد من التفاصيل.

لقد قرأت للتو أن العقود الآجلة 0.2 ليست نهائية كما اعتقدت ، لذلك ربما لا يزال من الممكن بعد كل شيء إعادة تسمية Pin إلى PinMut وإضافة نسخة مشتركة.

RalfJung لقد قرأت

أعتقد أنك وجدت حالة استخدام محتملة لامتلاك متغير Pin غير قابل للتغيير ، لكنني لا أفهم تعليقاتك حول Deref و &Pin<T> <=> &&T . حتى لو كان من الممكن تحويل Pin<T> إلى &T بأمان ، فإن هذا لا يجعلها متكافئة ، لأنه لا يمكن تحويل &T إلى Pin<T> . لا أرى سببًا يجعل التحويل الآمن غير آمن (من خلال التخلص من الضمانة Deref ).

حاليًا الطريقة map في Pin لها التوقيع

pub unsafe fn map<U, F>(this: &'b mut Pin<'a, T>, f: F) -> Pin<'b, U>

ما هو سبب عدم وجود التالي؟

pub unsafe fn map<U, F>(this: Pin<'a, T>, f: F) -> Pin<'a, U>

كما هو الحال ، لا يمكنني تحويل Pin من نوع واحد إلى Pin من أحد حقولها دون تقصير العمر دون داع.

هناك مشكلة أخرى في طريقة map وهي أنه يبدو أنه من المستحيل تحويل Pin من الهيكل إلى اثنين Pin s ، كل واحد من حقل مختلف للبنية. ما هي الطريقة الصحيحة لتحقيق ذلك؟

لقد كنت أستخدم هذا الماكرو لذلك:

macro_rules! pin_fields {
    ($pin:expr, ($($field:ident),+ $(,)?)) => {
        unsafe {
            let s = Pin::get_mut(&mut $pin);
            ($(Pin::new_unchecked(&mut s.$field),)+)
        }
    };
}

في كثير من المناقشة حول Pin يبدو أن هناك افتراضًا بأن "إسقاط" دبوسًا على حقل خاص يجب اعتباره آمنًا. لا أعتقد أن هذا صحيح تمامًا. على سبيل المثال ، تعليق المستند على map يقرأ حاليًا:

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

ضمان أن "البيانات التي تُرجعها لن تتحرك طالما أن قيمة الوسيطة لا تتحرك" هو الوصف الصحيح للعقد الذي يجب على المتصل map أن يدعمه. يبدو أن الأصل "(على سبيل المثال ، لأنه أحد الحقول ذات القيمة)" يشير إلى أنه طالما أنك تقوم فقط بإرجاع مرجع إلى مجالك الخاص ، فأنت تضمن أنك آمن. لكن هذا ليس صحيحًا إذا قمت بتطبيق Drop . سيشاهد التضمين Drop &mut self ، حتى عندما يكون الرمز الآخر قد شهد بالفعل Pin<Self> . يمكنه متابعة استخدام mem::replace أو mem::swap للخروج من الحقول الخاصة به ، منتهكًا الوعد الذي قدمه استخدام "صحيح" سابق لـ map .

بمعنى آخر: باستخدام استدعاء "إسقاط رقم التعريف الشخصي الصحيح" إلى Pin::map (تبدو المكالمة مثل unsafe { Pin::map(&mut self, |x| &mut x.p) } ) ، مع عدم وجود استدعاءات أخرى unsafe ، يمكن للمرء أن ينتج غير سليم / غير محدد سلوك. هذا رابط ملعب يوضح ذلك.

هذا لا يعني أن هناك أي خطأ في API الحالي. يوضح هذا أنه يجب وضع علامة على Pin::map كـ unsafe ، وهو كذلك بالفعل. كما أنني لا أعتقد أن هناك خطرًا كبيرًا من تعثر الأشخاص عن طريق الخطأ في هذا الأمر عند تنفيذ العقود الآجلة أو ما شابه - عليك حقًا الخروج من حقلك الخاص في ضمانة Drop ، و أشك في أن هناك العديد من الأسباب العملية التي تجعلك ترغب في القيام بذلك.

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

  • لا ينبغي unsafe كلما تم ذلك.
  • وRFC يذكر أنه إذا Pin تحولت إلى &'a pin T ميزة اللغة، فإنه قد يكون "تافهة لمشروع من خلال المجالات". أعتقد أنني أظهرت أن هذا لا يزال يتطلب unsafe ، حتى لو كان يقتصر على إسقاط الحقول الخاصة.

تضمين التغريدة

حتى لو دبوسيمكن إلقاؤها إلى & T بأمان ، وهذا لا يجعلها متكافئة ، لأنه لا يمكن تحويل & T إلى Pin.

في الواقع ، هذا ليس شرطًا كافيًا. إنهم متساوون في نموذجي لأننا قمنا في رسالتي السابقة بالتعريف التالي:

التعريف الخامس: PinBox<T>.shr . مؤشر ptr ومدى الحياة "يلبي النوع المشترك PinBox<T> إذا كان ptr هو مؤشر للقراءة فقط إلى مؤشر داخلي آخر ، بحيث T.shr('a, inner_ptr)

(نوعًا ما ، ضمنيًا ، التعريف المقابل لـ Pin<'a, T>.shr .)
لاحظ كيف يعتمد PinBox<T>.shr على T.shr ولا شيء آخر. هذا يجعل PinBox<T>.shr هو نفس المتغير تمامًا مثل Box<T>.shr ، مما يعني أن &Box<T> = &PinBox<T> . يوضح المنطق المماثل أن &&T = &Pin<T> .

لذلك ، هذا ليس نتيجة لواجهة برمجة التطبيقات أو عقد مكتوب في طلب تقديم العروض. إنه نتيجة للنموذج الذي يحتوي فقط على ثلاثة أنواع: مملوكة ، مشتركة ، مثبتة. إذا كنت تريد المجادلة ضد &&T = &Pin<T> ، فعليك أن تجادل لتقديم نوع رابع ، "مشترك مثبت".

تضمين التغريدة

في كثير من المناقشات حول Pin يبدو أن هناك افتراضًا بأن "إسقاط" دبوس على حقل خاص يجب اعتباره آمنًا. لا أعتقد أن هذا صحيح تمامًا.

هذه نقطة جيدة جدا! فقط للتوضيح ، لا توجد مشكلة في جعل الحقل p Shenanigans عامًا ، هل هناك؟ في هذه المرحلة ، يمكن لأي عميل كتابة do_something_that_needs_pinning ، والهدف من RFC هو جعل ذلك آمنًا. (لا أعرف لماذا يذكر RFC المجالات الخاصة على وجه التحديد ، كان تفسيري دائمًا أنه من المفترض أن يعمل مع جميع المجالات.)

من المثير للاهتمام أن نرى كيف يتفاعل هذا مع تفسير drop في نموذجي . كتبت هناك أن drop(ptr) له شرط مسبق T.pin(ptr) . باستخدام هذا التفسير ، سيكون الرمز الفعلي المخطئ في مثالك هو التنفيذ drop ! (الآن أتساءل لماذا لم ألاحظ هذا بالفعل عند كتابة المنشور ...) أعتقد أننا نريد السماح بإسقاطات آمنة للحقول في نهاية المطاف ، ولا ينبغي لنا حقًا تقديم drop مع &mut إذا كان النوع يقوم بالتثبيت. من الواضح أن هذا زيف.

هل هناك أي طريقة يمكننا من خلالها إما (أ) جعل impl Drop غير آمن إذا كان النوع !Unpin ، أو (ب) تغيير توقيعه إلى drop(self: Pin<Self>) إذا T: !Unpin ؟ كلاهما يبدو بعيد المنال للغاية ، ولكن من ناحية أخرى ، سيحل كلاهما وجهة نظر Drop::drop لأخذ Pin<Self> ؛) لكن بالطبع في هذه المرحلة لم يعد هذا الحل متاحًا للمكتبة فقط. الجزء المحزن هو أنه إذا استقرنا كما هو ، فلن يكون لدينا توقعات ميدانية آمنة.

RalfJung لذلك أنا مهتم أكثر بالأسئلة العملية (كما تتوقع على الأرجح: wink :). أعتقد أن هناك نوعان:

  • هل يجب على Pin<T: !Unpin> تنفيذ Deref<Target =T> ؟
  • هل يجب أن يكون هناك كل من Pin<T> و PinMut<T> (السابق هو دبوس مشترك)؟

لا أرى سببًا للإجابة على السؤال الأول بشكل سلبي. أعتقد أنك قد أعيد فتحه للسؤال الثاني. أنا أميل إلى العودة إلى نوعين مختلفين من الدبابيس (لكني لست مقتنعًا تمامًا). إذا قمنا بترقية هذا إلى نوع مرجعي على مستوى اللغة ، فسيكون لدينا &pin T و &pin mut T .

يبدو أنه بغض النظر عما نقوم به ، ربما يحتاج نموذجك إلى حالة من النوع الرابع لتعكس بدقة ثوابت API. لا أعتقد أن تحويل &&T إلى &Pin<T> يجب أن يكون آمنًا.

لذلك أنا مهتم أكثر بالأسئلة العملية (كما تتوقع على الأرجح غمزة).

عادل بما يكفي. ؛) ومع ذلك ، أعتقد أنه من المهم أن يكون لدينا على الأقل فهم أساسي لما هو مضمون وما هو غير مضمون حول Pin بمجرد دخول رمز غير آمن إلى الصورة. كان لدى Basic Rust عدة سنوات من الاستقرار لمعرفة ذلك ، والآن نكرر هذا مقابل Pin في غضون بضعة أشهر. في حين أن Pin هو إضافة خاصة بالمكتبة فقط فيما يتعلق بالصياغة ، إلا أنها إضافة مهمة فيما يتعلق بالنموذج. أعتقد أنه من الحكمة أن نوثق بأكبر قدر ممكن من التفصيل ما هو الرمز غير الآمن ولا يُسمح له بافتراض أو القيام بحوالي Pin .

في حين أن هذه المخاوف نظرية في الوقت الحالي ، فإنها ستصبح فجأة عملية للغاية بمجرد أن يكون لدينا أول خطأ بسبب الكود غير الآمن الذي يضع افتراضات غير متوافقة.


بخصوص سؤالك الثاني:

يبدو أنه بغض النظر عما نقوم به ، ربما يحتاج نموذجك إلى حالة من النوع الرابع لتعكس بدقة ثوابت API. لا أعتقد أن تحويل && T إلى & Pinيجب أن تكون آمنة.

هذا ما كنت أتوقعه.

إذا أردنا أن نكون متحفظين ، فيمكننا إعادة تسمية Pin إلى PinMut لكن لا نضيف PinShr (من المحتمل أن يُطلق عليه Pin لكنني أحاول إزالة الغموض هنا ) في الوقت الراهن، وتعلن أن التعليمات البرمجية غير آمنة يمكن أن نفترض أن &PinMut<T> نقاط في الواقع إلى شيء يشبك. ثم سيكون لدينا خيار إضافة Pin كمرجع مشترك مثبت لاحقًا.

تم طرح سبب عملي لامتلاك PinShr من قبل comex : يجب أن يكون من الممكن توفير PinShr<'a, Struct> إلى PinShr<'a, Field> ؛ إذا استخدمنا &PinMut للدبابيس المشتركة التي لا تعمل. لا أعرف كم ستكون هناك حاجة إلى مثل هذه الكلمات.


بالنسبة إلى سؤالك الأول ، يبدو أن هناك بعض الحجج القوية المؤيدة: (أ) الخطط الحالية لأنواع الذات التعسفية الآمنة للكائنات ، و (ب) القدرة على استخدام الكم الهائل من واجهات برمجة التطبيقات الموجودة على المراجع المشتركة بأمان عند إجراء PinShr (أو PinMut ).

من المؤسف أنه لا يبدو أن لدينا طريقة سهلة لتقديم عمليات مماثلة يمكن أن تعمل على كل من &mut و PinMut ؛ بعد كل الكثير من التعليمات البرمجية التي تعمل على &mut ليس لديها نية لاستخدام mem::swap . (هذا ، كما أراه ، سيكون الميزة الرئيسية لحل قائم على !DynSized أو شيء مشابه: &mut سيتحول إلى نوع من المراجع التي قد تكون مثبتة أو غير مثبتة. نحن يمكن أن يكون هذا نوع مكتبة آخر في Pin API ، لكن هذا لا طائل منه نظرًا لأن لدينا بالفعل كل هذه الأساليب &mut هذه.)

هناك حجة خفيفة واحدة ضد ، وهي الأنواع التي تريد القيام بأشياء "مثيرة" لكل من &T و PinMut<T> . سيكون من الصعب القيام بذلك ، فليس كل شيء ممكنًا ويجب على المرء أن يكون شديد الحذر. لكنني لا أعتقد أن هذا يتفوق على الحجج الجيدة المؤيدة.

في هذه الحالة (على سبيل المثال ، مع impl Deref ) ، يجب أن يأتي PinShr<'a, T> مع طريقة آمنة تحوله إلى &'a T (مع الحفاظ على العمر الافتراضي).


وهناك قلق آخر أعتقد أنه يتعين علينا حله: قواعد Pin::map و / أو drop ، في ضوء ما لاحظته MicahChalmer أعلاه. لدينا خياران:

  • أعلن أن استخدام Pin::map مع إسقاط لحقل عام (بدون اشتراكات ، ولا حتى ضمنيًا) آمن دائمًا. هذا هو تفسيري لـ RFC الحالي. سيتطابق هذا مع &mut و & . ومع ذلك ، لدينا مشكلة حول Drop : يمكننا الآن كتابة رمز آمن يتسبب في UB نظرًا لنوع !Unpin الذي تم تشكيله جيدًا.
  • لا تفعل أي شيء مضحك حول Drop . ثم يتعين علينا إضافة تحذيرات صاخبة إلى Pin::map أنه حتى استخدامه في الحقول العامة قد يؤدي إلى عدم السلامة. حتى لو كان لدينا &pin T ، فلن نتمكن من استخدام ذلك للوصول إلى حقل في رمز آمن.

الحجة الوحيدة الممكنة للخيار الثاني التي يمكنني رؤيتها هي أنه قد يكون الخيار الوحيد الذي يمكننا تنفيذه بالفعل. ؛) أعتقد أنه أقل شأنا من كل طريقة ممكنة - فهو يجعل &pin غريبًا وغير مريح حتى لو أصبح مدمجًا في يوم من الأيام ، فهو مسدس قدم ، ويعيق التكوين.

قد تكون هناك طريقة لتحقيق الخيار الأول ، لكنها ليست تافهة وليس لدي أي فكرة عن كيفية جعلها متوافقة مع الإصدارات السابقة: يمكننا إضافة Unpin مرتبط بـ Drop ، وإضافة DropPinned ليس له حدود وحيث drop يأخذ Pin<Self> . ربما يمكن فرض Unpin المقيّد على Drop بطريقة غريبة حيث يمكنك كتابة impl Drop for S ، لكن هذا يضيف ضمنيًا إلى S يقول ذلك يجب أن يكون Unpin . ربما غير واقعي. : / (أعتقد أن هذه أيضًا نقطة يعمل فيها النهج القائم على !DynSized بشكل أفضل - فهو يحول &mut T إلى "قد يتم تثبيته أو لا يتم تثبيته" ، مع الاحتفاظ بـ drop صوت.)

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

في الواقع ، هذا هو الحال اليوم (باستخدام رمز غير آمن) يمكنك الانتقال من حقل من نوع !Unpin ، وهذا آمن ومحدد جيدًا طالما أنك لا تقوم أبدًا بإسقاط دبوس من هذا الحقل في مكان آخر . الاختلاف الوحيد مع Drop هو أن الجزء المتحرك يحتوي فقط على رمز آمن. يبدو لي أن الملاحظات الموجودة على Pin::map تحتاج إلى التغيير لتلاحظ أنه ليس آمنًا إذا خرجت من هذا المجال ، بغض النظر عن الضمنية Drop

يجب أن يكون الانتقال من الحقول من نوع !Unpin آمنًا في بعض الحالات ، لأن المولدات ستنتقل على الأرجح من أحد الحقول عند عودتها.

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

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

على سبيل المثال ، لن يُسمح لي حتى بالانتقال من Pin<Option<T>> إلى Option<Pin<T>> ما لم يذكر Option صراحة أنه لن يكون هناك Drop يفعل أي شيء " مضحك". لا يستطيع المترجم فهم هذه العبارة ، لذلك بينما يمكن أن يوفر Option طريقة مناسبة للقيام بذلك ، فإن فعل الشيء نفسه مع match يجب أن يظل عملية غير آمنة.

الاختلاف الوحيد مع Drop هو أن الجزء المتحرك يحتوي فقط على رمز آمن.

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

يجب أن يكون الانتقال من حقول نوع Unpin آمنًا في بعض الحالات ، لأنه من المحتمل جدًا أن تتحرك المولدات خارج أحد الحقول عند عودتها.

أفترض في هذه الحالة أن الحقل سيكون Unpin ؟ لذلك من المحتمل أن يكون لدينا قاعدة تنص على أن Pin::mut لحقل عام من بنية أجنبية جيدة إذا كان هذا الحقل من النوع Unpin . لست متأكدًا من مدى فائدة ذلك ، لكنه على الأرجح أفضل من لا شيء.

أريد أن أكرر بسرعة ارتباكي عن &Pin<T> عدم توفير المزيد من الضمانات من &&T . & و &mut و &pin على التوالي "وصول مشترك" و "وصول فريد" و "وصول فريد إلى قيمة لن يتم نقلها". إن فهم &&pin كـ "وصول مشترك إلى وصول فريد إلى نوع لن يتم نقله" يخبرك أن الذاكرة مشتركة (يتم إلغاء ضمان التفرد البالغ &pin من خلال مشاركة & ) ، لكنك ما زلت تحتفظ بالملكية التي لن يتم نقل النوع ، أليس كذلك؟

لست متأكدًا مما تطلبه أو تقوله. هل أنت مرتبك لماذا أعتقد أن "المشاركة المثبتة" هي وضع أساسي / كتابة في حد ذاتها؟

النقطة المهمة هي أن "الوصول المشترك" ليس شيئًا أعرف كيفية تحديده بمفرده. هناك العديد من الطرق المختلفة لمشاركة وتنسيق المشاركة ، كما يتضح من الطرق المختلفة جدًا ، على سبيل المثال ، Cell ، RefCell ، و Mutex حصة.

لا يمكنك أن تقول فقط أنك تشارك شيئًا ما ("إلغاء ضمان التفرد" لشيء ما) تملكه وتتوقع أن يكون هذا البيان منطقيًا. عليك أن تقول كيف تشارك ، وكيف تتأكد من أن هذا لا يمكن أن يسبب الخراب. يمكنك "المشاركة بجعلها للقراءة فقط" ، أو "المشاركة من خلال منح الوصول الذري فقط من خلال الأحمال / المخازن المتزامنة" ، أو "المشاركة [ضمن سلسلة محادثات واحدة فقط] من خلال جعل علامة الاستعارة هذه تنسق نوع الوصول الذي يجب توزيعه" . كانت إحدى النقاط الرئيسية في RustBelt هي إدراك أهمية السماح لكل نوع بتعريف ما يحدث لنفسه عند مشاركته.

لا أستطيع التفكير في طريقة لجعل "التثبيت المشترك" ينشأ مثل التكوين المتعامد للمشاركة والتثبيت. ربما توجد طريقة لتعريف مفهوم "آلية المشاركة" التي يمكن تطبيقها بعد ذلك على الثابت المملوك أو المثبت لإحداث "مشاركة (عادية)" و "مشاركة مثبتة" ، لكنني أشك في ذلك بجدية. أيضًا ، كما رأينا أن هذا يتراجع عن RefCell - إذا كان RefCell يفعل شيئًا مشابهًا لما يفعله للثابت المشترك فقط ، فلا يمكننا بالتأكيد تبرير ذلك من & &pin RefCell<T> عبر &RefCell<T> (باستخدام Deref ) عبر borrow_mut يمكننا الحصول على مرجع &mut الذي يشير إلى عدم حدوث تثبيت.

تضمين التغريدة

يمكننا إضافة Unpin مرتبط بـ Drop ، وإضافة DropPinned ليس له حدود وأين يأخذ Drop Pin.

هل يمثل تعريف Drop المشكلة هنا حقًا؟ هناك طريقة أخرى للتفكير في الأمر وهي إلقاء اللوم على mem::swap و mem::replace . هذه هي العمليات التي تتيح لك نقل شيء لا تملكه. افترض أنه تمت إضافة T: Unpin إلى _them_؟

بالنسبة للمبتدئين، من شأنه أن يحل drop الحفرة التي أشرت - بلدي Shenanigans ستفشل لتجميع، وأنا لا أعتقد أنني يمكن أن تجعل من انتهاك الوعود دبوس دون آخر unsafe . لكنها ستمكن أكثر من ذلك! إذا أصبح الحصول على مرجع &mut آمنًا إلى قيمة مثبتة مسبقًا في drop ، فلماذا قصرها على drop ؟

ما لم أفقد شيئًا ما ، أعتقد أن هذا سيجعل من الآمن اقتراض مرجع &mut من PinMut<T> أي وقت تريده. (أنا أستخدم PinMut للإشارة إلى ما يسمى ، في الوقت الحالي كل ليلة ، Pin ، لتجنب الالتباس مع المناقشة حول الدبابيس المشتركة.) PinMut<T> يمكن أن تنفذ DerefMut شروط ، بدلاً من T: Unpin .

من المؤسف أنه لا يبدو أن لدينا طريقة سهلة لتقديم عمليات مماثلة يمكن أن تعمل على & mut و PinMut ؛ بعد كل الكثير من التعليمات البرمجية التي تعمل على & ليس لديه نية لاستخدام mem::swap .

A DerefMut على PinMut سيصلح ذلك ، أليس كذلك؟ الكود الذي يهتم بالتثبيت ، ويتطلب PinMut ، يمكنه استدعاء الرمز الذي يعمل على &mut بأمان وسهولة. سيتم وضع العبء بدلاً من ذلك على الوظائف العامة التي _do_ تريد استخدام mem::swap هؤلاء يجب أن يكون لديهم Unpin مضاف إليهم ، أو استخدم unsafe وتوخي الحذر لا تنتهك شروط الدبوس.

ستؤدي إضافة هذه الحدود إلى swap و replace الآن إلى كسر التوافق مع الإصدارات السابقة للعودة إلى الإصدار المستقر الأول. لا أرى طريقة واقعية للوصول إلى هناك من هنا. لكن هل أفتقد بعض الثغرة الأخرى في التفكير في أنه كان من الممكن أن يكون الشيء الذي يجب فعله ، إذا كان هذا معروفًا فقط في الأيام قبل 1.0؟

كما هو الحال ، لا أرى أي حل أفضل مما قالته withoutboats - احتفظ بـ map غير آمن ، وقم بوضع رسالة في مستنداتها تحذر الأشخاص من الخروج من أي حقل تم تثبيته سابقًا في drop ضمني.

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

يؤدي استخدام unsafe دائمًا إلى فرض قواعد على الكود الآمن المحيط. والخبر السار هنا هو أنه على حد علمنا ، إذا كان لدى بنية pinnable طريقة لإبراز دبوس إلى حقل خاص ، يمكن فقط لـ drop استخدام ذلك لانتهاك العقد في رمز آمن. لذلك لا يزال من الممكن إضافة مثل هذا الإسقاط وتقديم مستخدمين لهذا الهيكل بواجهة برمجة تطبيقات آمنة تمامًا.

هل تعريف Drop هو المشكلة هنا حقًا؟

إن تحديد اللوم تعسفي إلى حد ما ، فهناك أشياء مختلفة يمكن للمرء تغييرها لسد فتحة السلامة. ولكن هل لدينا اتفاق على أن تغيير Drop كما اقترحت سيؤدي إلى حل المشكلة؟

هناك طريقة أخرى للتفكير في الأمر وهي إلقاء اللوم على mem :: swap و mem :: replace. هذه هي العمليات التي تتيح لك نقل شيء لا تملكه. لنفترض أن T: تم إضافة Unpin ملزمة لهم؟

حسنًا ، هذا سيجعل &mut T آمنًا بشكل عام للاستخدام لأنواع !Unpin . كما لاحظت ، لن نحتاج حتى إلى PinMut . يمكن تعريف PinMut<'a, T> في عرضك على أنه &'a mut T ، أليس كذلك؟
هذا هو في الأساس عرض ?Move الذي تم تجاهله سابقًا بسبب التوافق مع الإصدارات السابقة ومشكلات تعقيد اللغة.

يؤدي استخدام "غير آمن" دائمًا إلى فرض قواعد على رمز الأمان المحيط.

أنا لست متأكدا ماذا يعني لك. خارج حدود الخصوصية ، يجب ألا يكون هذا هو الحال ؛ لا يمكن للرمز غير الآمن أن يفرض أي شيء على عملائه.

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

نعم ، يمكن للأنواع الاشتراك في إعلان أمان العرض. لكن على سبيل المثال ، لن يفهم مدقق الاستعارة أن هذا وصول إلى حقل ، لذا في ضوء PinMut<Struct> لا يمكنك استخدام مثل هذه الأساليب للحصول على PinMut إلى حقلين مختلفين في نفس الوقت.

ولكن هل لدينا اتفاق على أن تغيير Drop كما اقترحت سيؤدي إلى حل المشكلة؟

أوافق ، هذا من شأنه إصلاحه.

لن نحتاج حتى إلى PinMut بعد الآن. يمكن تعريف PinMut <'a، T> في اقتراحك على أنه &' a mut T ، أليس كذلك؟

لا ، لا يزال مطلوبًا من PinMut<'a, T> التعهد بأن المرجع لن يتحرك مرة أخرى أبدًا. مع &'a mut T يمكنك الوثوق فقط بعدم تحركه مدى الحياة 'a . لا يزال هذا مسموحًا به ، كما هو الحال اليوم:

`` صدأ
الهيكل العاشر ؛
الضم! إلغاء التثبيت لـ X {}
fn takes_a_mut_ref (_: & mut X) {}

fnorrow_and_move_and_borrow_again () {
دع موت x = X ؛
take_a_mut_ref (& mut x) ؛
اسمحوا mut b = Box :: new (x) ؛
take_a_mut_ref (& mut * b) ؛
}
""

سيكون من الآمن الانتقال من PinMut<'a, T> إلى &'a mut T ولكن ليس العكس - PinMut::new_unchecked ستظل موجودة ، وستظل unsafe .

هذا هو أساسًا اقتراح ?Move الذي تم تجاهله سابقًا بسبب التوافق مع الإصدارات السابقة ومشكلات تعقيد اللغة.

كما أفهمها ، كانت مقترحات ?Move تحاول الوصول بالكامل إلى عدم الحاجة إلى PinMut ، من خلال تغيير قواعد اللغة الأساسية لمنع مقتطف الشفرة أعلاه (عن طريق جعل Unpin be a Move سمة.) لا أقترح أي شيء من هذا القبيل - اقتراحي هو أن أبدأ بالطريقة التي هي عليها تمامًا كل ليلة الآن ، بالإضافة إلى:

  • أضف Unpin مرتبطًا بالوظائف std::mem::swap و std::mem::replace
  • قم بإزالة الربط Unpin من الأداة DerefMut لـ Pin ( PinMut إذا حدثت إعادة التسمية)

هذا كل شيء - لا تتغير لغة أساسية في كيفية عمل الحركات ، أو أي شيء من هذا القبيل. ادعائي هو: نعم ، سيكون هذا تغييرًا مفاجئًا ، ولكن سيكون له تأثير أقل من تغيير Drop (والذي بدوره لا يزال أقل حدة من مقترحات ?Move ) ، مع الاحتفاظ بالعديد من الفوائد. على وجه الخصوص ، سيسمح بإسقاط دبوس آمن (على الأقل للحقول الخاصة ، وأعتقد أنه حتى للعامة؟ لست متأكدًا تمامًا) ويمنع المواقف التي يجب فيها كتابة رمز مواز للعمل مع PinMut و &mut .

لا ، لا يزال يتعين على PinMut <'a، T> التعهد بأن المرجع لن يتحرك مرة أخرى. باستخدام & 'a mut T ، يمكنك الوثوق فقط بعدم تحركه مدى الحياة' أ.

أنا أرى. من المنطقي.

كما أفهمها ، كانت اقتراحات "النقل" تحاول الوصول إلى الطريق الصحيح لعدم الحاجة إلى PinMut ، من خلال تغيير قواعد اللغة الأساسية لحظر مقتطف الشفرة أعلاه (عن طريق جعل Unpin سمة نقل.)

فهمت.

ادعائي هو: نعم ، سيكون هذا تغييرًا جذريًا ، لكن سيكون له تأثير كسر أقل من تغيير Drop

أي تغيير تريد إسقاطه تقصد؟ إضافة Unpin ملزم؟ قد تكون محقًا ، لكن ليس لدي أي فكرة عن مدى انتشار استخدام mem::swap و mem::replace .

على وجه الخصوص ، سيسمح بإسقاط دبوس آمن (على الأقل للحقول الخاصة ، وأعتقد أنه حتى للعامة؟ لست متأكدًا تمامًا)

لا أرى كيف يمكن أن تحدث فرقًا هنا. كيف يسمح المجال العام بأقل من مجال خاص؟

لكن نعم ، يبدو أن هذا متماسك بشكل عام. سيظل Future يأخذ PinMut لأنه يجب أن يعتمد على الأشياء التي لا تتحرك أبدًا ، ولكن سيكون لديها مجموعة واسعة من الأساليب المتاحة للاستخدام.

ومع ذلك ، فإن جانب التوافق كبير. لا أعتقد أن هذا أمر واقعي ، فقد يؤدي إلى كسر كل الكود العام الذي يستدعي mem::swap / mem::replace . بالإضافة إلى ذلك ، الشفرة غير الآمنة الآن مجانية لتنفيذ هذه الطرق بنفسها باستخدام ptr::read / ptr::write ؛ قد يؤدي ذلك إلى كسر صامت للشفرة غير الآمنة الحالية. أعتقد أن هذا أمر محظور.

بينما نحن في موضوع تقديم Unpin ملزم على mem::swap و mem::replace (ولا نشعر بالقلق إزاء الكسر). إذا افترضنا أن مسار "المترجم المدمج" مأخوذ. هل سيكون من الممكن أيضًا تقديم نفس الحد على mem::forget لضمان تشغيل المدمرات للمتغيرات المثبتة على المكدس مما يجعل الصوت thread::scoped وتجنب "التبرز المسبق في ملابسك" في بعض الحالات؟

لاحظ أن mem::forget على PinBox مسموح به. الضمان الجديد المقترح المتعلق بالإسقاط لا يقول "الأشياء لا تتسرب". تقول "لا يتم إلغاء تخصيص الأشياء دون استدعاء drop أولاً". هذا بيان مختلف جدا. هذا الضمان لا يساعد thread::scoped .

لإضافة سياق ، فإن نقل البيانات من هيكل يستخدم Future هو أمر أفعله بشكل شائع. غالبًا ما تحتاج إلى إجراء أعمال تنظيف إذا لم يتم اقتراع المستقبل حتى الاكتمال (تم إسقاطه قبل اكتمال الاقتراع).

لذلك ، من المؤكد أنني كنت سأصل إلى هذا اللغم الأرضي عند نقل الكود الحالي إلى العقود الآجلة 0.3 حتى مع إضافة الوثائق إلى map .

carllerche توضح الوظيفة map أنه لا يجب عليك استخدام هذا لنقل أي شيء. لا يمكننا ولا نريد الحماية من الأشخاص الذين ينتقلون عمدًا من Pin<T> ، لكن عليك الابتعاد عن طريقك (باستخدام رمز غير آمن) للقيام بذلك. لن أسمي ذلك لغم أرضي.

إذن ، إلى أي لغم أرضي تقصد؟

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

إذا لم يكن من الممكن القيام بذلك ، أعتقد أن معظم واجهات برمجة التطبيقات القابلة للاستخدام التي تستخدم التثبيت يجب أن تستخدم PinShare. قد لا يكون هذا عائقًا كبيرًا ، على ما أعتقد ، لكنني ما زلت غير واضح بشأن العلاقة مع Unpin في هذه الحالة. على وجه التحديد: لنفترض أنني حصلت على مرجع ذي دبابيس وحصلت على مرجع لحقل على النوع (لفترة حياة معينة). هل يمكنني حقًا الاعتماد على عدم تحركه بمجرد انتهاء هذا العمر؟ ربما يمكنني ذلك إذا كان الحقل !Unpin لذا فربما يكون الأمر جيدًا ، طالما أن Pin لا يمكنه الخروج - أنا قلق في الغالب بشأن الأرقام. ما لم تكن تقول إنه حتى التثبيت المشترك لا يمكن أن يكون آمنًا بدون إصلاح الإسقاط - في هذه الحالة ، أعتقد أن إصلاح الإسقاط للعمل مع التثبيت يجب أن يحدث بشكل أساسي ؛ وإلا فإنها تصبح ميزة مكتبة متخصصة لا يمكن استخدامها بأمان حقًا ، ولا تستحق (IMO) أي مكانة في اللغة الأساسية ، حتى لو كانت مفيدة جدًا لـ Futures.

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

بدافع الفضول: ما هي الحجج ضد إضافة Unpin مرتبط بـ Drop ؟ لقد استشهدت بالتوافق العكسي ، مع كون البديل هو أنك تحتاج إلى ربط الشيء الذي تم إسقاطه تلقائيًا بطريقة أو بأخرى ، ولكن قم بإسقاط قيود مستوى نظام الكتابة الغريبة بالفعل التي لا توجد لسمات أخرى - لماذا هذا مختلف تمامًا؟ من المؤكد أنها ليست أنيقة مثل مجرد إجراء قطرة تأخذ Pin<T> لكن لا يمكننا في الواقع إجراء هذا التغيير في هذه المرحلة. هي القضية أننا لا نعرف في الواقع ما يجب القيام به إذا كنت تفعل انخفاض مكالمة على النوع الذي يحتوي فقط على Unpin التنفيذ، عندما يكون نوع نفسه !Unpin ؟ أعتقد أن طرح استثناء في تطبيق Drop في هذه الحالة قد يكون هو النهج الصحيح ، لأن أي شخص يعتمد على drop للتشغيل لأنواع عامة يحتاج بالفعل إلى التعامل مع حالة الذعر. قد يعني ذلك أنه سيكون من الصعب جدًا استخدام أنواع !Unpin عمليًا بدون قيام مجموعة من الأشخاص بتحديث التعليمات البرمجية الخاصة بهم لاستخدام سمة Drop (لذلك سيضطر النظام البيئي إلى نقل كل شيء إلى thew new version) ، لكنني أعتقد أنني سأكون على ما يرام مع ذلك لأنه سيظل يحتفظ بالسلامة ولن يكسر الكود الذي لم يستخدم !Unpin على الإطلاق. بالإضافة إلى ذلك ، فإن الشيء "الخاص بك كود الذعر إذا لم يتم ترقية المكتبة" من شأنه أن يحفز الناس حقًا على المضي قدمًا!

في الواقع ، هذا هو تصميمي المقترح:

قم بتمديد سمة الإسقاط باستخدام طريقة ثانية تأخذ Pinكما اقترحت. قم بتنفيذ افتراضي متخصص where T: Unpin call drop (أفترض أن هذا سوف يجتاز قواعد التخصص الحالية؟ ولكن حتى لو لم يحدث ذلك ، يمكن دائمًا أن يكون Drop بغلاف خاص ؛ بالإضافة إلى Drop عادةً ما يتم إنشاء وظائف الآن لديك نفس السلوك الذي اقترحته أعلاه تمامًا ، مع عدم وجود مشكلات توافق عكسي ولا محاولة لاشتقاق حد تلقائيًا بشكل محرج.

لديها مشكلة أن مكتبات الطرف الثالث يجب أن تقوم بالترقية لكي تكون مفيدة عمليًا مع أنواع !Unpin ، لكن كما قلت ، يمكن القول أن هذا أمر جيد. على أي حال ، ليس لدي أي فكرة عن عدد تطبيقات drop بالفعل بتغيير حقولها بطرق تتطلب &mut معظم تلك التي يمكنني التفكير فيها تفعل أي شيء مثير للاهتمام إما استخدام unsafe أو استخدم قابلية التغيير الداخلية ، لكنني على الأرجح لا أفكر في بعض الاستخدامات الشائعة.

الشيء الرئيسي في هذا النهج هو أنه إذا تم اتخاذه ، فلا بد من أخذه قبل استقرار Pin . هذا سبب آخر آمل حقًا ألا يتم التسرع في دفع Pin . لا أعتقد أننا اكتشفنا عواقب التصميم بشكل كافٍ.

(أرى قضية واحدة أكثر المحتملة: ManuallyDrop والاتحادات في المتوسط العام أن أي شخص يمكن ربما قد كتبت المدمر على نوع عام التي لم نفترض أن drop تنفيذ داخلها لا يمكن أن الذعر ، لأنه ببساطة لم يكن قادرا على تشغيل (كما لن يسمح لاستدعاء أي الأخرى &mut وظائف على T عام، باستثناء تلك التي تنفذ على الصفات غير الآمنة التي وعد بعدم الذعر). ومنذ Pin ing نوع يجب أن يضمن بالفعل تشغيل تطبيق الإسقاط قبل تدمير ذاكرته [وفقًا للدلالات الجديدة] ، أعتقد أن النوع !Unpin داخل ManuallyDrop المستخدم لهذا الغرض في الكود الحالي لا يمكن اعتباره مثبتًا في المقام الأول ، لذلك يجب أن يظل الكود الحالي صحيحًا إذا كان يقوم بهذا الافتراض ؛ بالتأكيد ، لا يجب أن تكون قادرًا على عرض دبوس بأمان في ManuallyDrop ، منذ ذلك الحين يمكن أن يكون آمنًا فقط إذا ضمنت استدعاء المدمر الخاص به قبل الهبوط. لا أعرف كيف يمكن للمرء أن ينقل هذه الحالة إلى الشركات هل يمكن أن يستفيد هذا من عناصر "eyepatch" على الإطلاق ، حيث يبدو أنها مخصصة لغرض مماثل؟]. لست متأكدًا حقًا من أن هذا يطير من الناحية المعنوية ، ولكنه ربما يعمل لأغراض تنفيذ Drop للكود الحالي.

فيما يتعلق بموضوع eyepatch ، على الرغم من ... ما زلت غير متأكد من التعريف الرسمي الدقيق له ، ولكن إذا كانت الفكرة العامة هي أنه لا توجد وظائف عامة مثيرة للاهتمام يتم استدعاءها على T ، فربما يمكن للمرء استغلال ذلك أكثر؟ وهذا يعني أنه إذا كان drop_pinned التنفيذ لل !Unpin تم تنفيذه نوع، الحاوية تحترم eyepatch أن تتصرف بشكل صحيح. ويبدو من الممكن لي أن واحدا يمكن أن الفشل الوقت ثم الترجمة ل !Unpin الأنواع التي تنفذ Drop ، لا تنفذ drop_pinned ، ولا eyepatch المعلمات الخاصة بهم عامة، في بالطريقة نفسها التي نستخدمها عند استخدام حاويات غير قابلة للضبط مع عمر مرجعي ذاتي.

تشكل البيانات الوجودية خطر التوافق مع الإصدارات السابقة مع أي استراتيجية وقت تجميع ؛ لهذا السبب أعتقد أن الحل الذي يفشل في وقت التشغيل أكثر واقعية. لن يؤدي هذا إلى فشل وقت التشغيل ولن يكون له إيجابيات خاطئة.

تحرير: في الواقع، نقطة الصفر كل ما سبق: نحن فقط بحاجة فعلا إلى القلق حول الحقول في متناول تالفة، لا &mut وصول عموما، صحيح؟ لأننا قلقون فقط بشأن التوقعات الميدانية الضمنية من &mut self .

ربما أقوم بإعادة اختراع عرض !DynSized ، ولكن بشكل أساسي: يجب أن تكون الحاويات العامة نفسها !Unpin إذا كانت تكشف عن أي طرق تهتم بنوع الدبوس (أدرك أن هذا يبدو بشكل مريب مثل المعلمات غير الصحيحة حجة ، ولكن اسمعني!). لا تعكس المستندات الوجودية مثل Box<Trait> و &mut Trait حالتهم Unpin بالضرورة ، لكن (على الأقل من التحديق في واجهات برمجة التطبيقات الحالية؟) لا أعتقد أن Pin<Box<T>> و Pin<&mut T> بالضرورة قابلة للإكراه على Pin<T> حيث T: !Unpin ؛ عدم تنفيذ ذلك يعني أن الإشارات المثبتة إلى هذه الأنواع لن توفر وصولاً مُثبتًا بأمان إلى الأجزاء الداخلية الخاصة بهم (لاحظ أن هناك بعض السوابق لهذا الأمر مع العلاقة بين & mut و &: & mut & T ليست قابلة للإكراه على & mut T ، فقط & T ، و Box <& mut T> غير قابل للإكراه على Box<T> ، فقط & mut T ؛ بشكل عام ، عندما تصطدم أنواع مختلفة ، لا يلزم نشرها تلقائيًا). أدرك أنه عادةً ما يكون &mut T و Box<T> و T قابلين للتبادل تمامًا من منظور نموذجي ، ولا تمتد هذه الحجة إلى الوجود الافتراضي المضمّن ، ولكن ربما هذا هو مضمون اقتراح DynSize (لا تسمح بالمبادلات الآمنة أو mem::replace s لقيم كائن السمات ، على ما أعتقد؟ في الواقع ، هذا غير مسموح به بالفعل ... لكنني أفترض أن هناك سبب ما قد يجعل هذا يتغير في المستقبل). هذا يجعل حل وقت التجميع واضحًا جدًا: لأي هيكل يمتلك ( Box أو مؤشرات أولية - لا يمكن لأي منها نشر وصول Pin بأمان ، باستثناء & للدبوس المشترك ، ولكن لا يمكن نقل & على أي حال - أو إذا قررت استخدام حل "لا يمكن الانتقال من كائنات السمات" ، يمكن أيضًا التحقق من الحقول بشكل انتقالي على طول &mut و Box ما أظن) ولديه حق الوصول (بمعنى الرؤية) إلى نوع !Unpin المعروف (بما في ذلك نفسه) ، سيتعين عليه تنفيذ النوع الثاني من drop . يبدو لي أن هذا جيد تمامًا ولا يمثل توافقًا عكسيًا على الإطلاق ، نظرًا لعدم وجود أنواع مستقرة هي !Unpin قد يضطر الناس إلى إعادة تطبيق المدمرات بعد تحديث المكتبة ، لكن هذا كل شيء ، أليس كذلك؟ علاوة على ذلك ، ستبقى تفاصيل التنفيذ الداخلية داخلية ؛ فقط إذا تم الكشف عن حقل !Unpin ، فستكون هناك أية مشكلات. أخيرًا ، ستستمر جميع أنواع الحاويات العامة (ليس فقط Vec وعناصر المكتبة القياسية ، ولكن بشكل أساسي النظام البيئي crates.io بأكمله) في العمل بشكل جيد. ما هو الشيء الكارثي الذي أفتقده في هذا الحل؟

(في الواقع ، يبدو لي أنه حتى لو لم تتمكن من فرض هذا في وقت تعريف drop ، يجب أن تكون قادرًا على الأقل على القيام بذلك في وقت إنشاء مثيل ، مثل dropck يفعل ، نظرًا لأنك تحتاج فقط إلى القلق بشأن الأنواع التي تم إنشاء مثيل لها بالكامل).

إعادة قراءة عرض Dynsized : لاحظت أن الحجة ضده كانت تتطلب أن تكون الأنواع الثابتة دائمًا DynSized حتى قبل تثبيتها. أنا أزعم أعلاه أننا نحتاج حقًا إلى القلق بشأن هذا فقط في حالة كائنات السمات ؛ قد يكون من الممكن (على الرغم من صعوبة تبريره) فرض هذا الإكراه على نوع !Unpin على سمة يتطلب ربطها صراحة بـ + ?DynSized (أو أيًا كان ؛ يمكن أن يتم ذلك تلقائيًا ، أفترض). بينما قد تكون هناك العديد من الحالات التي يكون فيها حجم يجب أن يكون معروفا لل !Unpin أنواع (في الواقع، ليس لدي مثل هذه الحالة استخدام!)، أو أنها يجب أن تكون قادرة على أن تبادلت قبل معلقة أنها، وآمل هناك عدد قليل جدًا من مثل هذه الحالات للديكورات الداخلية لكائنات السمات المصنوعة من أنواع غير منقولة (الحالات الوحيدة التي لدينا الآن هي لأشياء مثل التحويل Box -> Rc الذي نريد منعه صراحة ، أليس كذلك؟ في الواقع ، ليس من الواضح بالنسبة لي أن تعرض size_of_val هو مشكلة هنا أم لا ، لأنني ما زلت لا أعرف ما إذا كان من المتوقع أن تتمكن من تحويل Pin<'a, Box<Trait> إلى Pin<'a, Trait> ، ولكن إذا لم تستطع الاعتماد على Sized بالطبع). يريد المرء حقًا أن يكون قادرًا على ربطهم بـ !Unpin في أي حال ولكن كما قلت ، أعتقد أن الناس يريدون تجنب إضافة سمات سلبية أكثر مما نحتاج إليه بالفعل (شخصياً ، أتمنى أن يكون !Unpin ستكون كائنات السمات نادرة بما يكفي ومتخصصة بحيث أن ربط كائنات السمات بـ ?Unpin بدلاً من Unpin سيكون معقولًا تمامًا ولن يصيب نظام الكتابة كثيرًا ؛ معظم الاستخدامات مقابل !Unpin يمكنني التفكير في أنه لن يكون هناك تطبيقات مفيدة لمعظم السمات الحالية ، حيث أنهم يريدون عمومًا تنفيذ إجراءات على Pin<self> . كما تريد عادةً استخدامها مع PinBox أو PinTypedArena أو أيًا كانت النقطة التي تبدو عندها حدود ?Unpin طبيعية جدًا).

لدي تصميم جديد ، والذي أعتقد أنه ليس فظيعًا مثل التصميم القديم: https://github.com/pythonesque/pintrusive/blob/master/src/main.rs. بدلاً من محاولة جعل إسقاطات الدبوس تعمل في كل مكان ، يسأل هذا التصميم كيف يمكننا التعامل مع رمز Rust "القديم" الذي لا يعرف أي شيء عن التثبيت ، من كود Rust "الحديث" الذي يريد دائمًا دعم التثبيت. يبدو أن الإجابة الواضحة هي استخدام السمة RalfJung المقترحة من PinDrop ، ولكن فقط للأنواع التي لديها إفلات مخصص ، وتريد عرض الحقول.

تختار الأنواع صراحةً إسقاط الحقول (المقابلة لاشتقاق سمة PinFields ) ، والتي تُشبه الكود المكتوب بلغة Rust "الحديثة" ؛ ومع ذلك ، فإنه لا يتطلب أي متطلبات إضافية على الكود في Rust "القديم" ، وبدلاً من ذلك يختار دعم الإسقاط إلى العمق 1 فقط لأي اشتقاق معين لـ PinFields . كما أنه لا يحاول نقل Pin خلال المراجع ، والتي أعتقد أنها ربما فكرة جيدة ألا تفعلها على أي حال. وهو يدعم الهياكل والتعدادات ، بما في ذلك أي تحليل لفك الارتباط ، يجب أن يكون Rust قادرًا على توفيره ، من خلال إنشاء بنية ذات حقول ومتغيرات متطابقة ، ولكن مع الأنواع التي تم استبدالها بمراجع Pin 'd للأنواع (يجب أن تكون تافهة لتمديد هذا إلى Pin و PinMut عند إجراء هذا التغيير). من الواضح أن هذا ليس مثاليًا (على الرغم من أنه من المأمول أن يتمكن المحسن من التخلص من معظمه) ، إلا أنه يتمتع بميزة أنه يعمل مع BARTSK و NLL ويعمل بسهولة مع التعداد (على عكس الموصلات التي تم إنشاؤها).

تعمل حجة الأمان من خلال التأكد من عدم تنفيذ Drop للهيكل على الإطلاق ، أو التأكد من أنه إذا تم تنفيذ Drop له ، فهو تطبيق تافه لا يستدعي سوى PinDrop (نسخة من Drop تأخذ Pin). أعتقد أن هذا يستبعد جميع مشكلات السلامة المتعلقة بإسقاط الحقول المثبتة ، بعلامة استفهام واحدة: شاغلي الرئيسي هو إيجاد حجة جيدة لسبب فصل الحقول في نفس الحاوية بالضبط (أي الحقول في العمق 1) التي يمكن أن تبطل بعضها البعض الدبابيس الموجودة في المدمرات الخاصة بهم ، ستكون بالفعل غير صالحة بدون إسقاطات دبوس. أعتقد أنه يمكنني تبرير ذلك إذا استطعنا إثبات أنه يمكنك أيضًا فعل الشيء نفسه إذا تم احتجازهم في PinBoxes منفصلة ، مما يعني أن المكان الذي يعيشون فيه جزء من عقد الأمان الخاص بهم ؛ هذا يعني أن أدوات التدمير الخاصة بهم ليست آمنة بمعزل عن غيرها وأن بنائها خارج الوحدة يتطلب رمزًا غير آمن. بعبارة أخرى ، يتوقف صحتها على تنفيذ نوع الحاوية ، مما يعني أنه ينبغي أن يكون من المقبول طلب المزيد من المدمر أكثر مما نطلبه للحصول على رمز آمن تعسفي.

pythonesque لم أتابع حقًا ما كتبته أعلاه حول DynSize ، لكنني أعتبر أن كل شيء قديم الآن على أي حال؟ لذلك ، سأعلق فقط على آخر مشاركة لك.

للتلخيص ، أنت تقول إن الإسقاط على حقل هيكل / تعداد (بما في ذلك حقول pub ) غير آمن بشكل عام ، ولكن يمكن للنوع الاشتراك في توقعات المجال الآمن من خلال عدم تنفيذ Drop . إذا كان النوع يريد أداة تدمير ، فيجب أن تكون PinDrop بدلاً من Drop :

trait PinDrop {
  fn pin_drop(self: PinMut<Self>);
}

لدينا بالفعل بحث مطبعي عن Drop لرفض الانتقال من حقل ، لذلك لا يبدو من الواقعي التحقق أيضًا من Drop لرفض الإسقاط من خلال &pin . بالطبع ، سيظل فحص "الخروج من الحقل" مرفوضًا إذا كان هناك PinDrop ، بينما سيسمح بالإسقاط في هذه الحالة.

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

هل هذا يلخص الاقتراح؟ الجزء الذي لا أفهمه هو الفقرة الأخيرة ، هل يمكنك إعطاء مثال يوضح ما يقلقك؟


الآن ، بالطبع أنا أتساءل ما هي التزامات الإثبات هنا. أعتقد أن أسهل طريقة لمعرفة ذلك هي أن نقول أن PinDrop هو في الواقع النوع الرئيسي والوحيد من أداة التدمير الموجودة رسميًا ، و impl Drop for T هو في الواقع سكر نحوي مقابل impl PinDrop الذي يستدعي الطريقة غير الآمنة PinMut::get_mut ثم يستدعي Drop::drop . وهذا هو الذي تم إنشاؤه تلقائيا كود غير آمنة، ومع ذلك، لأن تعلق هو "تمديد المحلي" (أي أنها غير معكوس متوافق مع رمز غير آمنة الحالية)، هذا الرمز غير آمنة هو دائما آمنة إذا لم نوع معين يهتمون تعلق.

بشكل أكثر رسمية قليلاً ، هناك "ثابت تثبيت افتراضي" يكون لدى الأنواع إذا كان مؤلفها لا يهتم بالتثبيت. الأنواع التي تستخدم ثابت التثبيت الافتراضي هي تلقائيًا Unpin . تؤكد كتابة impl Drop لنوع يحتوي على ثابت مخصص أن النوع يستخدم "ثابت التثبيت الافتراضي". هذا مريب إلى حد ما لأنه يبدو قليلاً كما لو كان هناك التزام إثبات هنا ، ولكن لا يوجد unsafe للتحذير من هذا - وبالفعل هذا ليس مثاليًا ، لكن التوافق مع الإصدارات السابقة مهم ، فماذا يمكنك فعل. إنها ليست كارثة أيضًا ، لأنه يمكن للمرء أن يجادل بأن ما يعنيه هذا حقًا هو أنه يغير التزام الإثبات الذي يتم تكبده على رمز غير آمن في مكان آخر ، إلى الحد الذي يجب أن يعمل فيه هذا الرمز غير الآمن مع "ثابت التثبيت الافتراضي". إذا لم يكن هناك رمز غير آمن في مكان آخر في هذه الوحدة ، فكل شيء على ما يرام.

يمكنني حتى أن أتخيل أنه يمكننا إضافة نسج مقابل impl Drop for T ما لم يكن T: Unpin ، ربما يقتصر على الحالة التي تحتوي فيها الوحدة النمطية التي تحدد النوع على رمز غير آمن. سيكون هذا مكانًا لتثقيف الأشخاص حول هذه المشكلة وتشجيعهم على إما unsafe impl Unpin (يؤكد رسميًا أنهم يستخدمون ثابت التثبيت الافتراضي) ، أو impl PinDrop .

تضمين التغريدة

هل هذا يلخص الاقتراح؟

نعم ، أكثر أو أقل (أعتقد أن اقتراحك في الواقع أكثر طموحًا بشكل ملحوظ من مقترح لأنه يبدو أنه يقترح تنفيذ التوقعات تلقائيًا إذا لم يتم تنفيذ Drop ، وهو ما أعتقد أنه من المحتمل أن يكون مشكلة توافق إلى الأمام للمكتبات ؛ ولكن ربما هناك طريقة ما ذلك).

الجزء الذي لا أفهمه هو الفقرة الأخيرة ، هل يمكنك إعطاء مثال يوضح ما يقلقك؟

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

أعتقد أن أسهل طريقة لمعرفة ذلك هي القول بأن PinDrop هو في الواقع النوع الرئيسي والوحيد من المدمر الموجود رسميًا ، وأن Drop Drop for T هو في الواقع سكر نحوي لـ PinDrop الذي يستدعي الطريقة غير الآمنة PinMut :: get_mut ثم يستدعي قطرة :: قطرة.

متفق عليه. لسوء الحظ ، لم يكن هذا الطريق مفتوحًا للحل الحالي لأنه يحاول القيام بأشياء في المكتبة.

بشكل أكثر رسمية قليلاً ، هناك "ثابت تثبيت افتراضي" يكون لدى الأنواع إذا كان مؤلفها لا يهتم بالتثبيت. يتم إلغاء تثبيت الأنواع التي تستخدم ثابت التثبيت الافتراضي هذا تلقائيًا. كتابة impl Drop لنوع يحتوي على ثابت مخصص يؤكد أن النوع يستخدم "ثابت التثبيت الافتراضي". هذا مريب إلى حد ما لأنه يبدو قليلاً كما لو كان هناك التزام إثبات هنا ، ولكن لا يوجد خطر للتحذير من هذا - وفي الواقع هذا ليس مثاليًا ، لكن التوافق مع الإصدارات السابقة مهم لذا ما الذي يمكنك فعله.

نعم ، هذا ما يدور في ذهني تقريبًا ... أنا قلق فقط لأنني لا أملك حدسًا جيدًا لما قد يعنيه إضفاء الطابع الرسمي على ضمان "الرمز غير الآمن في الوحدة". تعجبني فكرتك عن الوبر (أحب أي شيء يجذب المزيد من الأشخاص باستخدام PinDrop ، في الواقع!) لكنني أعتقد أن unsafe impl Unpin ربما يكون خطأ في كثير من الأحيان حتى يكون اقتراحًا جيدًا على الأقل بالنسبة للأنواع ذات المجالات العامة العامة (ولكن مرة أخرى ، بالنسبة للهياكل التي لا تحتوي على مثل هذه الحقول ، سيكون هذا صحيحًا في كثير من الأحيان ... إنه صحيح إلى حد كبير بالنسبة للمكتبة القياسية ، على سبيل المثال)

لقد كتبت مثالاً عن كيفية عمل #[derive(PinnedFields)] : https://github.com/withoutboats/derive_pinned

لقد رأيت الناس يؤكدون أن المشتقات مثل هذه "ليست سليمة" ولكن أفيك هذا غير صحيح. ستحتاج إلى استخدام رمز غير آمن للقيام بشيء يتعارض مع الكود الذي تم إنشاؤه بواسطة هذا المشتق - أي أن هذا يجعل الكود غير الآمن الآخر غير سليم (وأعتقد أن هذا الرمز - يتحرك حول بيانات ?Unpin - هو شيء يمكنك / يجب دائمًا تجنبها).

تحرير: حسنًا ، اقرأ بالفعل المشاركات القليلة الأخيرة حول المدمرين. سوف معالجة.

withoutboats نعم ، أعتقد أنك رأيت هذا بالفعل ، لكن المشكلة كانت أن الكود غير الآمن في نوع !Unpin الصحيح يمكن إبطال مفعوله بواسطة مدمر آمن على نوع اشتق PinFields (لذا لم يكن هناك رمز غير آمن في الوحدة النمطية للنوع الذي اشتق PinFields باستثناء الأشياء التي تم إنشاؤها تلقائيًا). هذا هو الجزء الإشكالي. ألقِ نظرة على التصميم الذي ربطته ، رغم ذلك (تجاهل الخيار الأسلوبي لإنشاء هيكل منفصل بدلاً من اشتقاق ملحقات فردية - كان ذلك فقط أحاول جعله يدعم أكبر عدد ممكن من حالات الاستخدام باستخدام مدقق الاستعارة). كنت قلقًا لبعض الوقت ، لكنني الآن متأكد تمامًا من أن #[derive(PinFields)] لا يزال بإمكانه العمل ، فهو يتطلب فقط الحرص على التأكد من أن Drop لم يتم تنفيذه بشكل مباشر.

أريد أيضًا أن أتطرق إلى نقطة أخرى كنت أفكر فيها (لم أرها حتى الآن يتم حلها بشكل مباشر في أي مكان؟): أعتقد أن شيئًا من شأنه أن يجعل Pin أكثر قابلية للاستخدام ويتكامل بشكل أفضل في الكود الحالي ، سيكون ذلك هو النزول بقوة على جانب جميع أنواع المؤشرات بشكل أساسي كونها Unpin . أي ، جعل &mut T ، &T ، *const T ، *mut T ، Box<T> ، وهكذا كلها تعتبر Unpin لأي T . على الرغم من أنه قد يبدو مريبًا السماح بشيء مثل ، على سبيل المثال ، Box<T> ليكون Unpin ، فمن المنطقي عندما تفكر أنه لا يمكنك الحصول على Pin إلى داخل Box من أصل Pin<Box<T>> . أعتقد أن السماح فقط لـ !Unpin بإصابة الأشياء "المضمنة" هو أسلوب معقول جدًا - ليس لدي حالة استخدام واحدة للسماح بالتثبيت ليصبح فيروسيًا عبر المراجع ، وهو يجعل دلالات أي نوع &pin نهائيًا ممتعًا للغاية (لقد وضعت جدولًا لكيفية تفاعله مع أنواع المؤشرات الأخرى في هذا السيناريو ، وإذا تجاهلت الحركات بشكل أساسي ، فإنه يجعل &mut pin يتصرف مثل &mut ، &pin تتصرف بنفس الطريقة & ، و box pin تتصرف بنفس الطريقة box فيما يتعلق بالعلاقة مع المؤشرات الأخرى). كما أنه ليس مهمًا من الناحية التشغيلية ، بقدر ما أستطيع أن أقول: بشكل عام ، فإن نقل قيمة من النوع A تحتوي على مؤشر إلى قيمة من النوع B لا يحرك القيمة المشار إليها لـ اكتب B ، ما لم تكن قيمة النوع B مضمنة في القيمة من النوع A ولكن إذا كان الأمر كذلك ، فإن A يكون تلقائيًا !Unpin لأنه يحتوي على B بدون اتجاه المؤشر. ربما الأهم من ذلك ، أن هذا يعني أن نسبة كبيرة من الأنواع التي تحتاج حاليًا إلى تنفيذ يدوي غير آمن !Unpin لن تحتاج إلى واحد ، نظرًا لأن معظم المجموعات تحتوي فقط على T خلف مؤشر غير مباشر . ومن شأن ذلك أن يسمح لكل من التيار Drop لمواصلة العمل، وتسمح هذه الأنواع لتنفيذ PinDrop دون تغيير معاني الكلمات (منذ ما إذا كان النوع هو Unpin فإنه يمكن علاج Pin وسيطة &mut أي حال).

قد أفتقد بعض الأسباب التي تجعل هذا النوع من التثبيت المتعددي فكرة جيدة ، لكن حتى الآن لم أجد أيًا منها. الشيء التشغيلي الوحيد الذي قد أكون قلقًا بشأنه هو ما إذا كان من الممكن فعلاً تنفيذ هذا السلوك بسمات تلقائية - أعتقد أنه من المحتمل أن يحدث ذلك ، ولكن ربما في بعض الحالات حيث يستخدم الأشخاص PhantomData<T> ولكن لديهم بالفعل ملكية المؤشر إلى T ، سيكون من الجيد لهم تغييره إلى PhantomData<Box<T>> . عادةً ما نفكر في هؤلاء على أنهم متماثلون تمامًا من الناحية المعنوية ، ولكن مع التثبيت هذا ليس صحيحًا تمامًا.

pythonesque إن مصطلحات "لغة جديدة" وهذا نوع من التأثير على الموقف بالنسبة لي. انطباعي عما تفعله هو:

  • بشكل افتراضي ، يُنشئ #[derive(PinFields)] أداة غير متاحة Drop . يضمن هذا عدم وصولك مطلقًا إلى الحقل المثبت في أداة التدمير.
  • تقوم سمة اختيارية بتغيير هذا Drop لاستدعاء PinDrop::pin_drop ، ومن المفترض أن تقوم بتطبيق PinDrop .

هل هذا صحيح؟


أعتقد أيضًا أن هذا الأمر برمته مهم فقط إذا قمنا بتمديد ضمانات Pin لدعم المجموعات المتطفلة. هل هذا يتفق مع فهمك RalfJung وpythonesque؟


كل هذا محبط إلى حد ما ، لأن ما يبدو واضحًا هو أن Drop يجب أن يأخذ نفسه بمقدار Pin ! يبدو إجراء تغيير أكثر جذرية (ربما حدثًا) أمرًا جذابًا ، لكنني لا أرى طريقة لا تسبب اضطرابًا كبيرًا.

في ما يلي مثال على نوع الوصول غير السليم إلى الحقول المثبتة + الإفلات الذي يمكن أن يتسبب في: https://play.rust-lang.org/؟gist=8e17d664a5285e941fe1565ce0eca1ea&version=nightly&mode=debug

يقوم النوع Foo بتمرير مخزن مؤقت داخلي لنوع من واجهة برمجة التطبيقات الخارجية التي تتطلب بقاء المخزن المؤقت في مكانه حتى يتم إلغاء ارتباطه بشكل صريح. على حد علمي ، فإن هذا يبدو cramertj ، بعد إنشاء Pin<Foo> فأنت تضمن عدم نقله إلا بعد استدعاء Drop عليه (مع بشرط أنه يمكن تسريبه بدلاً من ذلك ولا يتم استدعاء Drop أبدًا ، ولكن في هذه الحالة نضمن أنه لن يتم نقله أبدًا).

ثم يكسر النوع Bar هذا عن طريق تحريك Foo أثناء تنفيذه Drop .

أستخدم هيكلًا مشابهًا جدًا لـ Foo لدعم جهاز طرفي لاسلكي يتصل عبر DMA ، يمكنني الحصول على StableStream مع مخزن مؤقت داخلي يتم كتابته بواسطة الراديو.

تضمين التغريدة

هل هذا صحيح؟

نعم ، باستثناء أنه لا يُنشئ فعليًا أمرًا غير عملي Drop (لأن الأنواع التي لا تنفذ Drop في Rust تعمل بشكل أفضل بشكل عام). بدلاً من ذلك ، يحاول التأكيد على أن Drop لم يتم تنفيذه باستخدام بعض ميزات المكتبة المشبوهة (فهو يعمل على مستقر ولكنه لا يخضع للتخصص - أعتقد أن هناك متغيرًا يجب أن يعمل تحت التخصص ولكنه لا يعمل الآن لأنه من القضايا مع الثوابت المرتبطة). إذا تم جعلها ميزة لغوية ، فسيكون من السهل جدًا فرض ذلك.

أعتقد أيضًا أن هذا الأمر برمته مهم فقط إذا قمنا بتوسيع ضمانات Pin لدعم المجموعات المتطفلة. هل هذا يتفق مع فهمك ralfj و @ pythonesque؟

لا ، هذا ليس هو الحال ، للأسف. لا علاقة للمثال المضاد المرتبط أعلاه بالضمانات الإضافية للمجموعات المتطفلة. يجب أن يكون النوع Pin 'd قادرًا بالفعل على افتراض أنه لن يتحرك قبل استخدامه مرة أخرى حتى بدون هذا الضمان ، لأنه إذا تم استدعاء طريقة مرتين على قيمة من خلف مرجع مثبت ، فستكون القيمة لا توجد طريقة لمعرفة ما إذا كان انتقل بين المكالمتين. تضيف الضمانات الإضافية اللازمة لجعلها مفيدة للمجموعات المتطفلة شيئًا إضافيًا حول الاضطرار إلى استدعاء drop قبل تحرير الذاكرة ، ولكن حتى بدون هذا الضمان drop لا يزال من الممكن استدعاؤها على شيء ما تم تثبيته حاليًا (خلف PinBox ، على سبيل المثال). إذا حدث الشيء الذي تم إسقاطه ليشمل الحقول المضمنة ، وسمحنا بإسقاط الدبوس من الشيء الذي تم إسقاطه إلى تلك الحقول ، فلا يزال بإمكان المدمر من النوع الخارجي تحريك الحقل الداخلي ، ثم قم بتثبيته مرة أخرى (عن طريق وضع المنقول للخارج قيمة الحقل في PinBox ، على سبيل المثال) ، ثم استدعاء الطرق التي تتوقع المراجع من وقت تثبيتها من قبل حتى تظل صالحة. لا أرى أي طريقة للتغلب عليها ؛ طالما يمكنك تنفيذ drop ، يمكن أن تواجه هذه المشكلة لأي حقل مضمّن !Unpin . لهذا السبب أعتقد أن الحل الأقل سوءًا هو عدم السماح للأشخاص بتنفيذ drop إذا كانوا يريدون التثبيت للعمل بشكل صحيح.

كل هذا محبط إلى حد ما ، لأن ما يبدو واضحًا هو أن Drop يجب أن يأخذ نفسه من Pin! يبدو إجراء تغيير أكثر جذرية (ربما حدثًا) أمرًا جذابًا ، لكنني لا أرى طريقة لا تسبب اضطرابًا كبيرًا.

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

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

الجزء الذي تم التأكيد عليه يبدو وكأنه صفقة كبيرة جدًا بالنسبة لي ؛ في الواقع ، يبدو أنه جوهر المشكلة.

في البداية تخيلت هذا الرمز ، والذي يستخدم طريقتنا الوحيدة التي تهتم بالفعل بالعناوين الداخلية:

struct TwoFutures<F>(F, F);

impl Drop for TwoFutures {
     fn drop(&mut self) {
          mem::swap(&mut self.0, &mut self.1);
          unsafe { Pin::new_unchecked(&mut self.0).poll() }
     }
}

لكنها تتضمن رمزًا غير آمن للعودة إلى Pin ! يجب اعتبار هذا الرمز غير سليم.

هل يمكننا استبعاد قدرة الأساليب التي تتطلب &self و &mut self للاعتماد على صحة العناوين الداخلية من أجل السلامة؟ كيف يبدو ذلك؟

withoutboats حتى إذا كانت الطرق التي تتطلب Pin<Self> تعتمد فقط على صلاحية العناوين الداخلية من أجل السلامة ، فلا يزال بإمكانك مواجهة المشكلة. يمكن للمدمر من النوع الخارجي mem::replace حقل النوع الداخلي (باستخدام مرجع &mut self ) ، ثم PinBox::new القيمة ، ثم استدعاء طريقة مثبتة عليه . لا حاجة غير آمنة. السبب الوحيد لعدم وجود مشكلة دون التمكن من عرض الحقول المثبتة هو أنها تعتبر مقبولة لنوع يقوم بالفعل بتنفيذ طرق غريبة !Unpin باستخدام unsafe (أو يشارك ثابتًا مع نوع يفعل ذلك ) للحصول على التزامات إثبات على تنفيذها drop حتى لو كان يستخدم رمزًا آمنًا هناك فقط. لكن لا يمكنك أن تسأل عن الأنواع التي تحتوي على نوع هو !Unpin وليس لها رمز غير آمن خاص بها.

تضمين التغريدة

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

حسنًا ، أعتقد أننا نريد أن نفعل ذلك في النهاية. كيف تكون هذه مشكلة التوافق؟

أنا قلق بشأن حقلين يعيشان مباشرة على نفس الهيكل ، أحدهما يغير الآخر عندما يُطلق عليه اسم Drop (ربما بالاعتماد على أشياء مثل أمر الإسقاط من أجل الأمان) بطريقة تنتهك ثوابت التثبيت في المجال الآخر ، ولكنه يحافظ على سلامتها الهيكلية (مما يتيح لك مشاهدة انتهاكات ثابت التثبيت).

لكن هذا سيكون غير قانوني بالفعل. يجب ألا تنتهك ثوابت الآخرين.

لكنني أعتقد أن الضمني غير الآمن قد يكون Unpin خطأ في كثير من الأحيان حتى يكون اقتراحًا جيدًا ، على الأقل بالنسبة للأنواع ذات المجالات العامة العامة

أعتقد أنه سيكون صحيحًا بالفعل في معظم الأوقات - أتوقع أن معظم الأشخاص لن يقدموا أي أدوات وصول مقابل Pin<Self> ، وفي هذه الحالة من المحتمل أنهم يستخدمون ثابت التثبيت الافتراضي وبالتالي لا بأس من unsafe impl Unpin لهم.

أعتقد أن شيئًا من شأنه أن يجعل Pin أكثر قابلية للاستخدام ويتم دمجه بشكل أفضل في الكود الحالي ، هو أن ينزل بحزم إلى جانب جميع أنواع المؤشرات التي تكون Unpin. هذا هو ، صنع & mut T ، & T ، * const T ، * mut T ، Box، وما إلى ذلك ، كل ذلك يعتبر Unpin لأي T. بينما قد يبدو مريبًا للسماح بشيء مثل ، على سبيل المثال ، Boxلكي تكون Unpin ، فمن المنطقي عندما تفكر في أنه لا يمكنك الحصول على دبوس إلى الجزء الداخلي للصندوق من خارج Pin>.

أوافق على أن هذا سيحدث على الأرجح (انظر أيضًا تعليقي على https://github.com/rust-lang/rust/pull/49621#issuecomment-378286959). أشعر حاليًا أننا يجب أن ننتظر قليلاً حتى نشعر بمزيد من الثقة في كل شيء التثبيت ، لكنني في الواقع لا أرى فائدة كبيرة في فرض التثبيت خارج حدود المؤشر غير المباشرة.
لست متأكدًا من شعوري حيال القيام بذلك مع المؤشرات الأولية ، إلا أننا عادة ما نكون متحفظين للغاية بشأن السمات التلقائية بالنسبة لهم حيث يتم استخدامها بكل أنواع الطرق المجنونة.


تضمين التغريدة

لقد كتبت مثالاً على كيفية عمل # [اشتقاق (PinnedFields)]: https://github.com/withoutboats/derive_pinned

pythonesque قال هذا بالفعل ، ولكن فقط مشكلة إسقاط MicahChalmer ، يمكنني كسر أي نوع مثبت يعتمد في الواقع على التثبيت. هذا مستقل عن الضمان الإضافي حول الاستدعاء. اسمحوا لي أن أعرف إذا كان لا يزال هناك حاجة إلى مثال. ؛)

تضمين التغريدة

تلك المكتبة غير سليمة.

للتوضيح ، فإن المشتق هو unsafe ، ولا يمكن استخدامه مع أداة يدوية Drop . باستخدام المشتق ، فإنك تتعهد بعدم القيام بأي من الأشياء السيئة المدرجة في تطبيق Drop .

https://github.com/rust-lang/rust/pull/50497 يغير واجهة برمجة تطبيقات التثبيت ، والأهم من ذلك أنه يعيد تسمية Pin إلى PinMut لإفساح المجال لإضافة Pin في المستقبل.


للتوضيح ، فإن المشتق غير آمن ، ولا يمكن استخدامه مع أداة Drop Drop اليدوية.

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

RalfJung حسنًا ، لا أعتقد أنه في الوقت الحالي. هناك خيار آخر فكرت فيه للتو وهو أن يكون لديك الإصدار "الآمن" لإنشاء تنفيذ Drop للنوع ، مما يحظر استخدامات يدوية أخرى بقيمة Drop . قد يكون هناك علامة unsafe لإيقاف هذا. WDYT؟

cramertj نعم يجب أن يعمل هذا أيضًا. ومع ذلك ، سيكون لها آثار جانبية مثل dropck الأكثر تقييدًا وعدم القدرة على الخروج من الحقول.

pythonesque وأجريت مكالمة يوم الاثنين لمناقشة هذا الأمر. هنا ملخص.

لقد توصلنا إلى أن السلوك "الصحيح" ربما كان لـ Drop لأخذ الذات من طرف. ومع ذلك ، فإن الانتقال إلى ذلك أمر شاق. على الرغم من أنه من الممكن مع تغيير الإصدار في شكل ما أعتقد ، فإن هذا سيكون مدمرًا للغاية.

التغيير المتوافق مع الإصدارات السابقة هو جعله غير متماسك إلى حد ما للأنواع التي يمكن أن تكون "مُسقَطَة من طرف" لتنفيذ Drop . في مستودعاته ، نفذpythonesque هذا من خلال إنشاء إشارات ضمنية من مجموعة معقدة من السمات.

يمكن للمرء أن يتخيل شكلًا مدمجًا أبسط قليلاً:

  • سمة علامة ، دعنا نسميها PinProjection ، تتحكم في ما إذا كان النوع يمكن أن يكون مسقطًا أم لا. من غير المتماسك (من خلال السحر المدمج في المترجم) تنفيذ كل من PinProjection و Drop .
  • سمة أخرى ، PinDrop ، تمتد PinProjection لتوفير مدمر بديل إلى Drop .

المشتق من إنشاء طرق إسقاط الدبوس مثل تلك التي أظهرتها pythonesque والتي ستولد أيضًا إشارات PinProjection للنوع.

تضمين التغريدة

خلصنا إلى أن السلوك "الصحيح" ربما كان أن يأخذ Drop على نفسه. ومع ذلك ، فإن الانتقال إلى ذلك أمر شاق. على الرغم من أنه من الممكن مع تغيير الإصدار في شكل ما أعتقد ، فإن هذا سيكون مدمرًا للغاية.

فقط أتساءل ، إذا أردنا القيام بمثل هذا الشيء يومًا ما ، فهل ما تقترحه متوافق مع المستقبل؟

على سبيل المثال ، لنفترض أننا قررنا ...

  • اجعل Drop يقبل بطريقة سحرية كل من النماذج المثبتة وغير المثبتة ، أو
  • أعد كتابة توقيعات الجميع Drop لهم كجزء من الترقية التلقائية إلى Rust 2021 :)

(أنا لا أقترح أيًا من هذين الأمرين ، ولكن يبدو من الصعب الإجابة على السؤال بدون أمثلة محددة).

tmandry هذه هي الفكرة الأساسية.

المشكلة الكبيرة هي تنفيذ Drop والذي يحرك معلمة النوع:

struct MyType<T>(Option<T>);

impl<T> Drop for MyType<T> {
    fn drop(&mut self) {
        let moved = self.0.take();
    }
}

نوع الترقية التلقائية التي تتحدث عنها سيؤدي فقط إلى كسر ضمنية Drop ، والتي تكون صالحة فقط إذا أضفنا حدًا T: Unpin .

لا أعرف أي حالات استخدام صالحة لنقل بيانات ?Unpin فعليًا في أداة التدمير ، لذا فهذه الحالة فقط ، حيث يمكن تقنيًا ولكن ليس المقصود بها ، هذه مشكلة حقًا.

تضمين التغريدة

سمة أخرى ، PinDrop ، توسع PinProject لتوفير مدمر بديل للإسقاط.

لماذا يمتد PinDrop PinProjection ؟ هذا يبدو غير ضروري.

يمكن أيضًا جعل هذا أكثر ذكاءً قليلاً بالقول إن PinProjection و Drop غير متوافقين ما لم يكن النوع Unpin (أو إيجاد طريقة أخرى لإزالة كل هذه الفروق / القيود الجديدة لأنواع Unpin ).

RalfJung نريد أن يكون PinDrop و Drop حصريًا بطريقة ما. في النهاية ، هناك عدة طرق لتنفيذ ذلك بمقايضات مختلفة ؛ أعتقد أننا بحاجة إلى RFC لذلك بغض النظر لأنه ليس تغييرًا بسيطًا.

withoutboats متفق عليه. اعتقدت أن التفرد يمكن أن ينشأ من معاملة خاصة PinDrop ، ولكن من الواضح أن هناك عدة طرق للقيام بذلك. أعتقد أنه سيكون من المفيد جدًا ألا تهتم الأنواع Unpin ؛ مع جعل جميع أنواع المؤشرات Unpin دون قيد أو شرط ، سيساعد هذا على الأرجح في تقليل عدد الحالات التي يتعين على الأشخاص معرفتها بهذا الأمر.

withoutboats من الجدير بالذكر أيضًا أن الحالات الرئيسية التي يمكنني التفكير فيها حيث يريد الأشخاص ، لنقل ، نقل البيانات العامة في مكان Vec في أداة تدمير (اخترت Vec لأن الأشخاص IIRC يرغبون في ذلك تنفيذ طرق على Vec تهتم فعليًا بـ Pin ، مما يعني أنه لا يمكن تنفيذ Unpin دون قيد أو شرط ، إنه في الواقع &mut Vec<T> أو Vec<&mut T> أو شيء من هذا القبيل. غالبًا ما أثير ذلك لأن هذه هي الحالات التي من المحتمل أن يتم RalfJung ، ونأمل بالتالي أن يتمكن الناس من الانتقال إلى PinDrop بسهولة في هذه الحالات بدلاً من unsafe impl Unpin (من الناحية النظرية ، يمكنهم دائمًا القيام بالأولى ، بالطبع ، من خلال ربط T بـ Unpin في النوع ، لكن هذا سيكون تغييرًا جذريًا لعملاء المكتبة).

أيضًا ، من الجدير بالذكر أنه في حين أن هذا يضيف عددًا قليلاً من السمات أكثر مما نرغب ، فإن السمات هي تلك الصفات التي لن تظهر أبدًا في توقيع نوع أي شخص. على وجه الخصوص ، PinProjection هو حد لا طائل منه تمامًا إلا إذا كنت تكتب ماكرو #[derive(PinFields)] لأن المحول البرمجي سيكون دائمًا (على ما أعتقد) قادرًا على تحديد ما إذا كان PinProjection معلقًا إذا كان ذلك ممكنًا اكتشف ماهية الحقول الموجودة في النوع ، والشيء الوحيد الذي تفيده هو عرض الحقول. وبالمثل ، لا يجب أن يكون PinDrop الأساس مرتبطًا بأي شيء ، لنفس السبب الذي يجعل Drop لا يستخدم أبدًا كقيد. حتى كائنات السمات يجب ألا تتأثر إلى حد كبير (ما لم نحصل يومًا ما على "حقول مرتبطة" على ما أعتقد ، ولكن مع ميزة جديدة مثل تلك يمكننا أن نفرض أن السمات مع الحقول المرتبطة كانت قابلة للتنفيذ فقط على أنواع PinProjection ، والتي من شأنها أن تحل بدقة هذه المشكلة).

تضمين التغريدة

حسنًا ، أعتقد أننا نريد أن نفعل ذلك في النهاية. كيف تكون هذه مشكلة التوافق؟

أفترض أنه ليس أكثر من مجرد إضافة نوع لا يستخدم Send أو Sync ، والفرق هو أن أيا من هذين النوعين لا يؤثر على اللغة الأساسية بنفس الدرجة التي قد يؤثر بها ذلك. يبدو القيام بذلك تلقائيًا مشابهًا لكيفية استخدام Rust في معالجة Copy (كانت الأنواع دائمًا Copy إذا كانت تحتوي فقط على أنواع Copy ولم تنفذ Drop ) التي تم تغييرها في النهاية لجعلها صريحة لأنه ، على الأرجح ، لم يعجب الناس حقيقة أن إضافة سمة ( Drop ) يمكن أن تكسر الكود العام دون أن يدركوا ذلك (لأن الهدف الكامل من التماسك هو ذلك إضافي لا ينبغي أن تؤدي تطبيقات السمات إلى كسر الصناديق النهائية). يبدو هذا متطابقًا تقريبًا ، فقط مع PinProjection بدلاً من Copy . لقد أحببت بالفعل السلوك القديم ، أعتقد أنه سيكون من الصعب تبرير عمل PinProjection بهذه الطريقة عندما لا يعمل Copy .

لكن هذا سيكون غير قانوني بالفعل. يجب ألا تنتهك ثوابت الآخرين.

نعم ، كلما فكرت في هذا الأمر ، كلما بدا أنه قد يمثل مشكلة أقل معقولية.

أعتقد أنه سيكون صحيحًا في الواقع في معظم الأوقات

حسنًا ، نعم ، ولكن فقط لأن معظم الأنواع لا تنفذ أي طرق تتطلب Pin أو تعرض حقولها العامة للجمهور. في حين أنه من غير المحتمل أن يتغير الأخير ، فإن الأول ليس كذلك - أتوقع على الأقل بعض أنواع stdlib التي هي حاليًا !Unpin لإضافة طرق إسقاط الدبوس ، وعند هذه النقطة لن يكون تنفيذ unsafe لم يعد صالحًا. لذلك يبدو لي أنه شيء هش جدًا. علاوة على ذلك ، أنا قلق من زيادة كمية الشفرة "المعيارية" غير الآمنة التي يتعين على الناس كتابتها ؛ أعتقد أن الحدود Send و Sync هي unsafe impl 'd بشكل صحيح بقدر ما يتم تنفيذها بشكل غير صحيح ، و Unpin ستكون أكثر مكرًا لأن عادةً لا يحتوي الإصدار الصحيح على أي حدود على T . لذلك يبدو أنه من الأفضل إلى حد كبير توجيه الناس نحو PinDrop (أنا أفهم سبب قلقك من القيام بذلك لأنواع المؤشرات الأولية ، على الرغم من ذلك. أنا قلق فقط من أن رمز unsafe سوف من المرجح أن تقوم بـ unsafe impl بدون تفكير ، ولكن كلما فكرت في الأمر أكثر كلما بدا أن الإعداد الافتراضي للمؤشرات الأولية صحيح على الأرجح وأن وضع علامة عليه باستخدام الوبر الخاص بك سيكون مفيدًا).

أعتقد أنه سيكون مفيدًا جدًا إذا لم تضطر أنواع Unpin إلى الاهتمام.

أوافق نوعًا ما ، ولكن بما أن الناس لن يستخدموا PinProjection كقيد فعلي ، فأنا لست متأكدًا من مدى أهمية ذلك في الممارسة. يوجد بالفعل تطبيق DerefMut مقابل PinMut حيث T: Unpin لذلك لن تحصل على أي فائدة منه اليوم. من المفترض أن تؤدي القاعدة المطبقة في المترجم (للتوقعات) إلى تحويل PinMut::new إلى نوع من إعادة الاقتراض لنوع Unpin &pin ، لكن هذا لا علاقة له بإسقاطات المجال حقًا. وبما أن اشتقاق PinProjection لا يتطلب PinProjection لحقوله ، فلن تحتاجه فقط لتلبية الحدود على derive لنوع آخر. حقاً ، ما هو المكسب؟ الشيء الوحيد الذي سيسمح لك بفعله هو تنفيذ Drop واشتقاق PinProjections في نفس الوقت ، لكننا نريد دائمًا أن يقوم الأشخاص بتنفيذ PinDrop إذا كان ذلك ممكنًا على أي حال. يكون صافي IMO سلبي.

لقد اخترت Vec لأن موظفي IIRC يرغبون في تنفيذ أساليب على Vec تهتم فعلاً بـ Pin ، مما يعني أنه لا يمكن تنفيذ Unpin دون قيد أو شرط

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

كما يجب علينا توخي الحذر بشأن الضمان drop ؛ يمكن أن يؤدي Vec::drain على سبيل المثال إلى تسريب الأشياء في حالة الذعر.

يبدو هذا متطابقًا تقريبًا ، فقط مع PinProject بدلاً من Copy

أوه ، الآن أفهم سؤالك. لم أكن أتحدث في الواقع عن الاشتقاق التلقائي PinProjections حيث أن اقتراحي لم يكن له سمة من هذا القبيل - ولكن في الواقع إحدى نتائج اقتراحي ستكون أن إضافة Drop هو تغيير مفاجئ إذا لديهم مجالات عامة.

الشيء الوحيد الذي سيسمح لك به حقًا هو تنفيذ Drop واشتقاق PinProjections في نفس الوقت ، لكننا نريد دائمًا أن يقوم الأشخاص بتطبيق PinDrop إذا كان ذلك ممكنًا على أي حال ، بحيث يكون ذلك صافي IMO سلبي.

كان هذا في الواقع هو الهدف. كلما قل اهتمام الناس بكل هذه الأشياء المثبتة ، كان ذلك أفضل.

سؤال التوضيح: في اقتراحك و PinDrop عنصر lang ويعامله المترجم كبديل لـ Drop ، أم أنه مجرد سمة مستخدمة بواسطة derive(PinProjections) لتنفيذ Drop ؟

كما يجب علينا توخي الحذر بشأن ضمان الإسقاط ؛ Vec :: يمكن أن يؤدي استنزاف على سبيل المثال إلى تسرب الأشياء في حالة الذعر.

حسنًا ، هذا صحيح ، لكنه لا يزيل تخصيص أي ذاكرة ، أليس كذلك؟ أن تكون واضحة، كنت تفضل في الواقع إذا Vec لم تنفذ دون قيد أو شرط Unpin لأنه سيجعل من السهل على الناس أن التبديل عادل ل PinDrop ، ولكن كان هناك بالتأكيد التحدث في بعض المناقشات المثبتة حول السماح لك بالتثبيت على عناصر الدعم. بمجرد أن تبدأ الحديث عن الحقول التي يتم تثبيتها ، يصبح من الواضح أنه لا يمكنك دائمًا تحويل Vec إلى Box<[T]> في مكانه ثم تثبيته ، لذلك قد يكون له بالفعل بعض القيمة عند هذه النقطة (على الرغم من أنه من الواضح أنه يمكنك أيضًا إضافة نوع PinVec بدلاً من النوع Vec ، كما حدث لـ Box ).

كان هذا في الواقع هو الهدف. كلما قل اهتمام الناس بكل هذه الأشياء المثبتة ، كان ذلك أفضل.

حسنًا ، من وجهة نظري ، سيكون هذا صحيحًا في البداية ، ولكن على المدى الطويل ، نرغب في ترحيل أكبر عدد ممكن من الأشخاص إلى القيمة الافتراضية PinDrop ، خاصةً إذا كان النوع يزعجك بالفعل #[derive(PinProjections)] (والذي ، مرة أخرى ، لن يكون مطلوبًا لأي غرض إذا كان النوع Unpin - ربما يحدث فقط في أشياء مثل الكود الذي تم إنشاؤه). ثم (ربما بعد أن ظل &pin في اللغة لبعض الوقت) يمكن أن يكون لديك تغيير في العصر الذي حول Drop إلى DeprecatedDrop أو شيء من هذا القبيل. بالنسبة إلى أنواع Unpin مجرد تغيير توقيع النوع من &mut إلى &mut pin سيحل كل شيء إلى حد كبير ، لذلك ربما لا تكون هناك حاجة لذلك.

سؤال التوضيح: في اقتراحك و

السابق.

يبدو أن مناقشة Drop هذه بأكملها بمثابة إشراف صارم جدًا نيابة عن RFC الأصلي. هل سيكون من المفيد فتح RFC جديد لمناقشة هذا الأمر؟ من الصعب إلى حد ما متابعة ماهية الاهتمامات ، والمخاوف الأكثر ضررًا من غيرها ، وكم عدد الآلات التي سنحتاج إلى إضافتها إلى اللغة أكثر مما كنا نظن في الأصل ، ومقدار الانكسار الذي يجب أن نتوقعه (وما إذا كان يمكن التخفيف من حدته من خلال الإصدار) في أسوأ السيناريوهات وأفضلها ، كيف يمكننا الانتقال إلى أي شيء ينجح في Drop ، وما إذا كان بإمكاننا أن نلعب على كل هذا ولا نزال نحقق أهدافنا لعام 2018 عبر بعض الوسائل الأخرى (مثل كيفية https: //github.com/rust-lang/rfcs/pull/2418 يقترح إخفاء Pin من جميع واجهات برمجة التطبيقات العامة من أجل عدم منع التثبيت).

bstrie أعتقد أن هناك مصدر قلق رئيسي واحد فقط ، لكنه &pin mut النهائي يمكن أن يجعل هذا النوع من الكود آمنًا في مرحلة ما:

struct Foo<T> { foo : T }

fn bar<T>(x: &pin mut Foo<T>) -> &pin mut T {
  &pin mut x.foo
}

من المهم أن يكون هذا النوع من الكود آمنًا لأنه من الناحية العملية ، فإن المراجع المثبتة ليست قابلة للتكوين حقًا بخلاف ذلك في كود آمن ، لذلك على سبيل المثال ، يتطلب تنفيذ أي من أدوات دمج العقود الآجلة استخدام unsafe . في ذلك الوقت ، كان يُعتقد أن استخدام unsafe يمكن رفعه في معظم الحالات ، وبالتالي لن يكون هذا أمرًا كبيرًا.

لسوء الحظ ، يعتمد ما إذا كان هذا آمنًا على تنفيذ Drop لـ Foo . لذلك إذا أردنا دعم توقعات المجال الآمن (سواء كان ذلك عبر ميزة اللغة ، أو مخصص #[derive] ، أو أي آلية أخرى) ، يجب أن نكون قادرين على ضمان أن مثل هذه التطبيقات السيئة Drop يمكن ' ر يحدث.

البديل الذي يبدو أنه يعمل بشكل أفضل يمكّن الكود أعلاه من العمل دون استخدام unsafe (مع إضافة #[derive(PinProjections)] على الهيكل) ولا يكسر أي كود موجود . يمكن إضافته بشكل عكسي حتى إذا كان Pin مستقر بالفعل ويمكن حتى إضافته كمكتبة نقية (بضربة مريحة شديدة). وهو متوافق مع كل من وحدات الماكرو #[derive] لإنشاء موصّلات ونوع المرجع الأصلي &pin . على الرغم من أنها تتطلب إضافة سمة جديدة واحدة على الأقل (وربما اثنتين ، اعتمادًا على كيفية تنفيذ الإصدار المثبت من Drop ) ، فإنها لا تحتاج أبدًا إلى الظهور في أي مكان في جمل أو كائنات سمة where ، و الإصدار الجديد من drop يحتاج فقط إلى أن يتم تنفيذه للأنواع التي لديها حاليًا تنفيذ Drop وتريد الاشتراك في إسقاطات الدبوس.

فقط لأن الحل يبدو أنه لا يحتوي على العديد من الجوانب السلبية لا يعني أنه ليس تغييرًا جوهريًا ، لذلك أعتقد أننا سننشئ بالتأكيد RFC لهذا الاقتراح (

إن اهتمام معظم الأشخاص بـ PinMut في واجهات برمجة التطبيقات العامة هو على وجه التحديد أنه سيتطلب إما unsafe أو خيوط عبر حدود Unpin كل مكان ، وهذا الاقتراح يحل هذه المشكلة. البدائل التي نوقشت في الصدأ لانغ / المراجع # 2418 يبدو أكثر إثارة للجدل من ذلك بكثير، سواء في الميكانيكا الفعلية من الطريقة التي يريد تجنب التعامل مع تعلق (الذي ينطوي على انتشار مختلف الصفات الأخرى التي سوف تظهر في واجهات برمجة التطبيقات العامة والوثائق) و في التعقيد الكلي للحل. في الواقع ، حتى لو تم حل التثبيت تمامًا ، أعتقد أن هناك عددًا من الأسئلة التي لا يشعر الناس أنها قد تم حلها بشكل مناسب باستخدام RFC ، لذلك أعتقد أن هناك على الأقل فرصة جيدة أن ينتهي الأمر بـ RFC الذي يضيف توقعات دبوس آمن قبلها.

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

  • هذا واحد (هل يمكننا أن نجعل حقول الإسقاط تتم في كود آمن؟). ليتم حلها من خلال RFC القادمة (حيث سيتم توضيح استراتيجيات التنفيذ الممكنة ، بالإضافة إلى البدائل الأخرى التي أخذناها في الاعتبار ولماذا تم تجاهلها). جميع أشكال الدقة المقترحة متوافقة مع الإصدارات السابقة.
  • هل يجب أن يتضمن تثبيت قيمة من النوع !Unpin باستخدام أداة التدمير اليدوي ضمانًا إضافيًا (عدم إبطال ذاكرة دعم القيمة إلا بعد استدعاء أداة التدمير) ، ويعرف أيضًا باسم "التغييرات للمجموعات المتطفلة"؟

    لا يزال بدون حل ، لأنه قد يكسر واجهة برمجة تطبيقات التثبيت المقترحة للمكدس ؛ إذا كان من الممكن إجراء واجهة برمجة تطبيقات تثبيت مكدس تحافظ على هذا الضمان للعمل مع غير متزامن / في انتظار يبدو أن معظم أصحاب المصلحة على استعداد لقبول ذلك (حاول شخص IIRC بالفعل حل هذا باستخدام المولدات ولكن rustc ICE'd).

    غير متوافق مع الضمانات القديمة ؛ إن طلب رمز غير آمن لفرض الضمان في الوقت الحالي متوافق مع كلتا النتيجتين المحتملتين ، ولكن فقط إذا لم يُسمح للرمز غير الآمن بالاعتماد على تطبيقه من أجل صحته. وبالتالي ، يتطلب هذا بالتأكيد الدقة بطريقة أو بأخرى قبل تثبيت التثبيت.

    لحسن الحظ ، يمكن لمستخدمي التثبيت مقابل Future s تجاهل هذا إلى جانب شكل واجهة برمجة تطبيقات التثبيت المكدسة. كومة تعلق API القائم على إغلاق متوافق مع أي قرار، لذلك Future المستخدمين عدم استخدام المتزامن / ننتظر يمكن استخدام API القائم على الإغلاق اليوم دون انتظار لهذا يحدد بعد. يؤثر القرار بشكل كبير على الأشخاص الذين يرغبون في استخدام التثبيت لأغراض أخرى (مثل المجموعات المتطفلة).

  • هل يجب أن تكون المؤشرات الأولية غير مشروطة Unpin ؟ ما زلت دون حل (أعتقد أنني الوحيد الذي اقترحه وما زلت إلى حد كبير 50-50 في ذلك). لن تكون متوافقة مع الإصدارات السابقة ؛ أنا أعتبر هذا مثيرًا للجدل في المقام الأول لهذا السبب.
  • هل يجب إنشاء أنواع المكتبات القياسية مثل Vec بدون شروط Unpin ، أم يجب إضافة موصّل حقل Pin ؟ لا يزال بدون حل ، وقد يحتاج إلى حل على أساس كل حالة على حدة. بالنسبة لأي نوع غلاف آمن معين ، فإن إضافة الملحقات أو جعل النوع غير مشروط Unpin متوافق مع الإصدارات السابقة.
  • هل يجب إزالة PinMut::deref ؟ يبدو أن الإجابة بشكل أساسي هي "لا" نظرًا لأن مزايا الاحتفاظ بها تفوق بكثير مساوئها ، ويبدو أن هناك حلولاً بديلة للحالات التي جعلت الناس يريدون ذلك في الأصل (على وجه التحديد ، Pin<RefCell<T>> ). تغييره سيكون غير متوافق.
  • كيف يجب أن نوفر أدوات وصول ميدانية على المدى القصير (تجاهل مشاكل الإسقاط)؟ لا يزال بدون حل: خياران تم تقديمهما حتى الآن هما https://github.com/withoutboats/derive_pinned و https://github.com/pythonesque/pintrusive. الدقة متوافقة تمامًا مع الإصدارات السابقة ، نظرًا لأنه بعد تغييرات الإسقاط يمكن القيام بذلك بشكل سليم في ماكرو بحت في رمز المكتبة.
  • كيف يجب أن نوفر موصّلات الحقول على المدى الطويل (على سبيل المثال ، هل يجب أن يكون هناك أنواع مخصصة من الصدأ &mut pin و &pin ؟ كيف يجب إعادة الاقتراض؟). لا يزال بدون حل ، ولكن متوافقًا مع جميع التغييرات المقترحة الأخرى (على حد علمي) ومن الواضح أنه يمكن دفعه إلى أجل غير مسمى.
  • هل يجب أن تكون الأنواع المشابهة للمراجع الآمنة بدون شروط Unpin ؟ يبدو أنه قد تم حله (نعم ، ينبغي). الدقة متوافقة تمامًا مع الإصدارات السابقة.
  • هل يجب أن يكون لدينا نوع Pin بالإضافة إلى Pin واحد فريد ، من أجل جعل الوصول إلى الحقول ممكنًا (لا يعمل مع &Pin لأن هذا مرجع إلى مرجع)؟ تم تغيير الدقة من Pin إلى PinMut وإضافة نوع Pin للحالة المشتركة. لم يكن هذا متوافقًا مع الإصدارات السابقة ، ولكن التغيير الفاصل ( Pin إلى PinMut ) تم إجراؤه بالفعل ، وأنا متأكد تمامًا من قبول هذا بالفعل بالفعل.

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

بالإضافة إلى الأسئلة أعلاه ، كانت هناك مقترحات أخرى تهدف إلى إعادة التفكير في التثبيت بطريقة أكثر جوهرية. الخياران اللذان أعتقد أنني comex لتقديم !Unpin أنواع !DynSized ، و steven099 (على المستوى الداخلي ؛ آسف ، لا أعرف اسم Github) اقتراح لديك نوع غلاف جديد غير بحجم Pinned يجعل الأجزاء الداخلية غير قابلة للحركة (نوع مثل غلاف ZST).

يعد الخيار !DynSized ميزة متحفظة إلى حد ما (بمعنى أن Rust لديه بالفعل سمة مماثلة متاحة) والتي لديها ميزة قد تكون ضرورية بالفعل للتعامل مع الأنواع غير الشفافة. بهذا المعنى ، قد يكون أقل توغلاً من التغييرات المقترحة على Drop . كما أن له جانبًا صعوديًا مرتفعًا: فهو يحل تلقائيًا المشكلة مع Drop لأن الأنواع !Unpin ستكون !DynSized وبالتالي لن يتمكن المرء من الخروج منها. سيؤدي ذلك إلى جعل &mut T و &T يعملان تلقائيًا كـ PinMut و Pin أينما كان T !DynSized ، لذلك لن لا نحتاج إلى تكاثر الإصدارات المثبتة من الأنواع والأساليب التي تعمل على &mut T يمكن غالبًا جعلها تعمل بشكل طبيعي (إذا تم تعديلها بحيث لا تتطلب DynSized ملزمة عندما لا يحتاجون إلى واحد ).

يبدو أن الجانب السلبي الرئيسي (إلى جانب المخاوف المعتادة حول ?Trait ) هو أنه لا يمكن نقل نوع !Unpin ، وهو مختلف تمامًا عن الوضع مع الأنواع المثبتة حاليًا. هذا يعني أن إنشاء نوعين مثبتين بدون استخدام مرجع لن يكون ممكنًا حقًا (بقدر ما أستطيع أن أقول ، على أي حال) ولست متأكدًا من كيفية أو ما إذا كان هناك أي قرار مقترح لهذا ؛ كان الهدف من IIRC هذا هو العمل جنبًا إلى جنب مع اقتراح &move ، لكنني لست متأكدًا من الدلالات المقصودة لذلك. كما أنني لا أفهم (من الاقتراح) كيف يمكنك الحصول على توقعات ميدانية آمنة معها ، لأنها تعتمد على أنواع مبهمة ؛ يبدو أنه يتعين عليك استخدام الكثير من التعليمات البرمجية غير الآمنة بشكل عام لاستخدامها.

النوع غير الحجم Pinned<T> مشابه إلى حد ما من حيث الروح ، لكنه يريد التغلب على المشكلة المذكورة أعلاه من خلال السماح لك بلف نوع في ZST يجعله ثابتًا (يتصرف بشكل فعال غير بحجم). لا يحتوي Rust على أي شيء يمكن مقارنته في الوقت الحالي: PhantomData لا يتضمن فعليًا مثيلًا من النوع ، والأنواع الأخرى ذات الحجم الديناميكي تولد مؤشرات دهنية ولا تزال تسمح بالتحركات (باستخدام واجهات برمجة التطبيقات التي تعتمد على size_of_val ؛ هذا هو ما كان يهدف ?DynSized إلى إصلاحه ، لذلك من المحتمل أن يكون هذا الاقتراح على ظهور هذه السمة مرة أخرى). لا يبدو لي أن هذا الاقتراح يعمل بالفعل على إصلاح مشكلة Drop إذا سمحت بإسقاطات آمنة ، ويبدو أيضًا أنه غير متوافق مع Deref ، لذلك بالنسبة لي المزايا التي تفوق Pin ليست واضحة جدا.

إذا كان من الممكن إجراء واجهة برمجة تطبيقات تثبيت مكدس تحافظ على هذا الضمان للعمل مع غير متزامن / في انتظار يبدو أن معظم أصحاب المصلحة على استعداد لقبول ذلك (حاول شخص IIRC بالفعل حل هذا باستخدام المولدات ولكن rustc ICE'd)

للإشارة ، ربما يشير هذا إلى https://github.com/rust-lang/rust/issues/49537 وهو من هذه المحاولة في واجهة برمجة تطبيقات تثبيت مكدس متداخلة قائمة على المولد. لست متأكدًا مما إذا كانت الحياة هناك ستنجح حتى لو تم حل ICE.

لا يزال بدون حل ، لأنه قد يكسر واجهة برمجة تطبيقات التثبيت المقترحة للمكدس ؛ إذا كان من الممكن إجراء واجهة برمجة تطبيقات تثبيت مكدس تحافظ على هذا الضمان للعمل مع غير متزامن / في انتظار يبدو أن معظم أصحاب المصلحة على استعداد لقبول ذلك (حاول شخص IIRC بالفعل حل هذا باستخدام المولدات ولكن rustc ICE'd).

أرى المكدس القائم على الإغلاق المثبت API كحل لهذا ، مع طريق مستقبلي (مرة واحدة &pin هي لغة بدائية) لشيء أكثر راحة وفحصه من قبل المترجم. هناك أيضًا هذا الحل القائم على الماكرو الذي اقترحهcramertj.

هل يجب إلغاء تثبيت المؤشرات الأولية بشكل غير مشروط؟ [...] لن تكون متوافقة مع الإصدارات السابقة ؛ أنا أعتبر هذا مثيرًا للجدل في المقام الأول لهذا السبب.

لماذا تعتقد أن إضافة impl Unpin for Vec<T> متوافق مع الإصدارات السابقة ولكن القيام بالشيء نفسه للمؤشرات الأولية ليس كذلك؟

يبدو أن الجانب السلبي الرئيسي (إلى جانب المخاوف المعتادة حول "سمة") هو أن نوع Unpin لا يمكن أبدًا نقله ، وهو ما يختلف تمامًا عن الوضع مع الأنواع المثبتة حاليًا. هذا يعني أن إنشاء نوعين مثبتين بدون استخدام مرجع لن يكون ممكنًا حقًا (بقدر ما أستطيع أن أقول ، على أي حال) ولست متأكدًا من كيفية أو ما إذا كان هناك أي قرار مقترح لهذا ؛ كان الهدف من IIRC هذا هو العمل جنبًا إلى جنب مع اقتراح & نقل ، لكني لست متأكدًا من الدلالات المقصودة لذلك.

حسنًا ، ضمن اقتراح المتغير @ steven099 (الذي Pinned<T> ، والذي يحتوي على T من حيث القيمة ولكنه !Sized ( وربما !DynSized ؛ التصميم الدقيق لتسلسل السمات مفتوح لتساقط الدراجات). ينتهي هذا في الواقع بمظهر مشابه جدًا للاقتراح الحالي ، باستثناء &'a mut Pinned<T> مقابل Pin<'a, T> . لكنها أكثر قابلية للتركيب مع الكود الحالي والمستقبلي الذي يكون عامًا على &mut T (مقابل T: ?Sized ) ، وهو أكثر توافقًا مع التصميم المستقبلي للأنواع غير المنقولة الحقيقية التي لن تضطر استخدم Pinned .

لمزيد من التفاصيل ، قد يبدو Pinned كما يلي:

extern { type MakeMeUnsized; }

#[fundamental]
#[repr(C)]
struct Pinned<T> {
    val: T,
    _make_me_unsized: MakeMeUnsized,
}

لن تقوم عمومًا بإنشاء Pinned<T> مباشرة. بدلاً من ذلك ، ستكون قادرًا على الإرسال من Foo<T> إلى Foo<Pinned<T>> ، حيث Foo هو أي مؤشر ذكي يضمن عدم نقل محتوياته:

// This would actually be a method on Box:
fn pin_box<T>(b: Box<T>) -> Box<Pinned<T>> {
    unsafe { transmute(b) }
}

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

في هذا المثال ، يرمز FakeGenerator إلى نوع منشئ مُولَّد من قبل مترجم:

enum FakeGenerator {
    Initial,
    SelfBorrowing { val: i32, reference_to_val: *const i32 },
    Finished,
}

بمجرد تثبيت قيمة FakeGenerator ، يجب ألا يكون رمز المستخدم قادرًا على الوصول إليها مباشرةً من خلال القيمة ( foo: FakeGenerator ) أو حتى عن طريق المرجع ( foo: &mut FakeGenerator ) ، لأن الأخير قد السماح باستخدام swap أو replace . بدلاً من ذلك ، يعمل رمز المستخدم مباشرةً مع ، على سبيل المثال ، &mut Pinned<FakeGenerator> . مرة أخرى ، هذا مشابه جدًا لقواعد PinMut<'a, FakeGenerator> للاقتراح الحالي. ولكن كمثال على قابلية أفضل للتركيب ، يمكن للضمين الذي تم إنشاؤه بواسطة المترجم استخدام سمة Iterator ، بدلاً من الحاجة إلى سمة جديدة تتطلب Pin<Self> :

impl Iterator for Pinned<FakeGenerator> {
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        /* elided */
    }
}

من ناحية أخرى ، بالنسبة للبناء الأولي ، يمكننا تسليم قيم FakeGenerator مباشرة والسماح لهم بالتنقل ، طالما أننا نضمن أنه لا يمكن الوصول إلا إلى حالات عدم الاقتراض الذاتي قبل التثبيت:

impl FakeGenerator {
    fn new() -> Self { FakeGenerator::Initial }
}

لذلك ، إذا أردنا إنشاء قيمتين FakeGenerator s من حيث القيمة ، يكون البناء مباشرًا:

struct TwoGenerators {
    a: FakeGenerator,
    b: FakeGenerator,
}

impl TwoGenerators {
    fn new() -> Self {
        TwoGenerators {
            a: FakeGenerator::new(),
            b: FakeGenerator::new(),
        }
    }
}

ثم يمكننا تثبيت الكائن TwoGenerators ككل:

let generator = pin_box(Box::new(TwoGenerators::new()));

ولكن ، كما ذكرت ، نحتاج إلى إسقاطات ميدانية: طريقة للانتقال من &mut Pinned<TwoGenerators> إلى &mut Pinned<FakeGenerator> (الوصول إما a أو b ). هنا أيضًا ، يبدو هذا مشابهًا جدًا للتصميم الحالي Pin . في الوقت الحالي ، سنستخدم ماكرو لإنشاء موصّل:

// Some helper methods:
impl<T> Pinned<T> {
    // Only call this if you can promise not to call swap/replace/etc.:
    unsafe fn unpin_mut(&mut self) -> &mut T {
        &mut self.val
    }
    // Only call this if you promise not to call swap/replace/etc. on the
    // *input*, after the borrow is over.
    unsafe fn with_mut(ptr: &mut T) -> &mut Pinned<T> {
        &mut *(ptr as *mut T as *mut Pinned<T>)
    }
}

// These accessors would be macro-generated:
impl Pinned<TwoGenerators> {
    fn a(&mut self) -> &mut Pinned<FakeGenerator> {
        unsafe { Pinned::with_mut(&mut self.unpin_mut().a) }
    }
    fn b(&mut self) -> &mut Pinned<FakeGenerator> {
        unsafe { Pinned::with_mut(&mut self.unpin_mut().b) }
    }
}

ومع ذلك ، تمامًا مثل التصميم الحالي ، سيحتاج الماكرو إلى منع المستخدم من تنفيذ Drop مقابل TwoGenerators . من ناحية أخرى ، من الناحية المثالية ، يسمح لنا المترجم بـ impl Drop for Pinned<TwoGenerators> . وهي ترفض هذا حاليًا مع وجود خطأ مفاده أن "تطبيقات Drop لا يمكن أن تكون متخصصة" ، ولكن يمكن تغيير ذلك. سيكون هذا أجمل قليلاً من IMO من PinDrop ، لأننا لن نحتاج إلى سمة جديدة.

الآن ، كملحق مستقبلي ، من حيث المبدأ ، يمكن للمجمع أن يدعم استخدام بناء جملة المجال الأصلي للانتقال من Pinned<Struct> إلى Pinned<Field> ، على غرار الاقتراح الموجود ضمن التصميم الحالي بأن المترجم يمكنه إضافة &pin T أصلي

ومع ذلك ، فإن "لعبتي النهائية" المثالية ليست ذلك ، ولكنها شيء أكثر دراماتيكية (وأكثر تعقيدًا في التنفيذ) حيث يدعم المترجم يومًا ما الأنواع الثابتة "الأصلية" ، بما في ذلك الأعمار الوجودية. شيء مثل:

struct SelfReferential {
    foo: i32,
    ref_to_foo: &'foo i32,
}

تتصرف هذه الأنواع مثل Pinned<T> ، في كونها !Sized وما إلى ذلك. [1] ومع ذلك ، على عكس Pinned ، فإنها لن تتطلب حالة مبدئية متحركة. بدلاً من ذلك ، سيعملون على أساس "نسخة مضمونة" ، حيث سيسمح المترجم باستخدام أنواع غير منقولة في قيم إرجاع الدالة ، والحرفية الهيكلية ، وأماكن أخرى مختلفة ، ولكن يضمن أنه سيتم بناؤها في مكانها تحت الغطاء. لذلك لا يمكننا فقط أن نكتب:

let sr = SelfReferential { foo: 5, ref_to_foo: &sr.foo };

(وهو ما يمكنك بالفعل القيام به نوعًا ما ) ... يمكننا أيضًا كتابة أشياء مثل:

fn make_self_referential() -> SelfReferential {
    let sr = SelfReferential { foo: 5, ref_to_foo: &sr.foo };
    sr
}

أو

let sr: Box<SelfReferential> = box SelfReferential { foo: 5, ref_to_foo: &sr.foo };

بالطبع ، ما سبق هو مجرد رسم تقريبي للغاية لما ستبدو عليه الميزة ؛ تحتوي البنية التي استخدمتها على مشكلات ، وسيكون بناء الجملة هو الأقل من بين العديد من التعقيدات التي ينطوي عليها تصميم الميزة. (بعد قولي هذا ، لقد فكرت في الأمر بما يكفي لأنني واثق تمامًا من إمكانية حل التعقيدات - إنها ليست مجرد فكرة غير متماسكة لا يمكن أن تنجح.)

أذكرها فقط كجزء من الدافع ، في الوقت الحاضر ، لاستخدام تصميم !DynSized -ish بدلاً من التصميم الحالي Pin . أعتقد أن &'a Pinned<T> أفضل بالفعل من Pin<'a, T> لأنه يتجنب الانفجار الاندماجي لأنواع المؤشرات. لكنها ترث بعض المشكلات نفسها:

  1. لا يدعم بشكل أساسي الأنواع التي تفتقر إلى الحالة المنقولة الأولية ، و
  2. إنه صاخب ومربك (يصعب شرح الفرق بين الإشارات المثبتة وغير المثبتة إلى نفس النوع) ، ويجعل الأنواع الثابتة تشعر بأنها من الدرجة الثانية. أريد أن تشعر الأنواع الثابتة والمرجعية الذاتية بأنها من الدرجة الأولى - لأنها مفيدة بطبيعتها ، ولجعل Rust يبدو أفضل للأشخاص القادمين من لغات أخرى ، حيث يكون جعل القيم المرجعية الذاتية أمرًا سهلاً وشائعًا.

في المستقبل الذي أتخيله ، ستحل الأنواع غير المنقولة الأصلية كلتا المشكلتين ، ولن تحتاج معظم الرموز أبدًا إلى استخدام كلمة "دبوس". (على الرغم من أن Pinned قد لا يزال لديه بعض حالات الاستخدام ، في الحالات التي لا يكون فيها إخراج النسخ جيدًا بما يكفي وتحتاج حقًا إلى حالة مبدئية متحركة.) في المقابل ، فإن Pin يخبز مفهوم افصل الحالة "غير المثبتة بعد" في تصميم كل سمة تستخدمها. و &pin المدمج سوف يخبزها في اللغة.

على أي حال ، يوجد هنا رابط ملعب لتصميم Pinned أعلاه.

[1] ... على الرغم من أننا قد نريد سمة جديدة ReallySized (مع اسم أقل سخافة) ، للأنواع ذات الحجم الثابت ولكنها قد تكون أو لا تكون قابلة للحركة. الشيء هو أنه سيكون هناك بعض الاضطراب هنا على أي حال ، لأنه مع دعم قيم rvalues ​​غير الحجم ، فإن العديد من الوظائف التي تتطلب حاليًا وسيطات Sized يمكن أن تعمل بشكل جيد مع تلك غير الحجم ولكن المنقولة. سنقوم بتغيير كل من حدود الوظائف الحالية والتوصيات بشأن الحدود التي يجب استخدامها للوظائف المستقبلية. أدعي أنه قد يكون من المفيد حتى تغيير الإصدار الافتراضي (الضمني) المرتبط بالأدوية العامة ، على الرغم من أن هذا سيكون له بعض الجوانب السلبية.

[تحرير: مثال على رمز ثابت]

تضمين التغريدة

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

لا يعمل النظام القائم على الإغلاق بشكل صحيح مع عدم التزامن / الانتظار ، على ما أعتقد ، لأنه لا يمكنك الاستسلام من خلال الإغلاق. ومع ذلك ، فإن النوع القائم على الماكرو مثير للاهتمام ؛ إذا كان هذا آمنًا حقًا ، فهو عبقري جدًا. لم أكن أعتقد أنه يمكن أن ينجح في البداية لأن الذعر أثناء إحدى القطرات في النطاق يمكن أن يؤدي إلى تسرب الآخرين ، ولكن يبدو أن هذا تم إصلاحه باستخدام MIR؟

لم أكن متأكدًا أيضًا من التفاعل بين ضمان "تشغيل أدوات التدمير قبل إلغاء تخصيص الذاكرة" والقدرة على عرض الحقول المثبتة ؛ إذا أصيب انخفاض المستوى الأعلى بالذعر ، فقد اعتقدت أن الحقول المثبتة المتوقعة في القيمة لن يتم تشغيل قطراتها. ومع ذلك ، في ملعب Rust ، يبدو في الواقع أن الحقول من هذا النوع لها قطراتها على أي حال حتى بعد الذعر من المستوى الأعلى ، وهو أمر مثير للغاية! هل هذا الضمان موثق بالفعل في أي مكان؟ يبدو من الضروري إذا كان تثبيت المكدس يعمل مع إسقاطات الدبوس (هذا ، أو شيء أكثر ثقلاً ، مثل PinDrop دائمًا ما يُجهض عند الذعر ، والذي يبدو غير مرغوب فيه لأنه سيقدم اختلافًا وظيفيًا بين Drop و PinDrop).

لماذا تعتقد أن إضافة أداة Unpin الضمنية لـ Vecمتوافق مع الإصدارات السابقة ولكن القيام بنفس الشيء بالنسبة للمؤشرات الأولية ليس كذلك؟

أرى وجهة نظرك: قد يعتمد شخص ما على التنفيذ التلقائي !Unpin من حقل مملوك Vec<T> ويقوم بتنفيذ موصّلاتهم الخاصة التي افترضت أن التثبيت كان متعدٍ لبعض !Unpin T . في حين أنه من الصحيح أن PinMut<Vec<T>> لا يوفر لك فعليًا أي طريقة آمنة للحصول على PinMut<T> ، لا يزال بإمكان الشفرة غير الآمنة استغلال PinMut::deref للحصول على مؤشرات أولية ووضع افتراضات حول كون المؤشرات مستقرة. لذلك أعتقد أن هذا موقف آخر حيث يكون متوافقًا مع الإصدارات السابقة فقط إذا كانت الشفرة غير الآمنة لا تعتمد على !Unpin كونها متعدية من خلال Vec (أو أيًا كان). ومع ذلك ، فإن هذا النوع من الاعتماد الشديد على التفكير السلبي يبدو مريبًا بالنسبة لي على أي حال ؛ إذا كنت تريد التأكد من أنك !Unpin حيث T لا يمكنك دائمًا إضافة PhantomData<T> (أعتقد أن هذه الوسيطة تنطبق أيضًا على المؤشرات الأولية). عبارة شاملة مثل "إنه UB لتفترض أن الأنواع الموجودة في المكتبة القياسية هي! قم بإلغاء التثبيت في رمز غير آمن ، بغض النظر عن معلمات النوع الخاصة بها ، ما لم يكن النوع واضحًا أو تعلن وثائقه أنه يمكن الاعتماد على هذا" قد يكون كافياً.

ومع ذلك ، فإن النوع القائم على الماكرو مثير للاهتمام ؛ إذا كان هذا آمنًا حقًا ، فهو عبقري جدًا. لم أكن أعتقد أنه يمكن أن ينجح في البداية لأن الذعر أثناء إحدى القطرات في النطاق يمكن أن يؤدي إلى تسرب الآخرين ، ولكن يبدو أن هذا تم إصلاحه باستخدام MIR؟

سيكون خطأ في MIR إذا أدى الذعر أثناء السقوط إلى تخطي إسقاط المتغيرات المحلية المتبقية. يجب أن ينتقل ذلك من "الهبوط العادي" إلى "الانخفاض المتراجع". ذعر آخر سيؤدي إلى إجهاض البرنامج.

تضمين التغريدة

سيكون خطأ في MIR إذا أدى الذعر أثناء السقوط إلى تخطي إسقاط المتغيرات المحلية المتبقية. يجب أن ينتقل ذلك من "الهبوط العادي" إلى "الانخفاض المتراجع". ذعر آخر سيؤدي إلى إجهاض البرنامج.

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

comex

الشيء الذي يدور حول Pinned (ما تقترحه) هو أنني أعتقد أنه إذا أردنا تنفيذه (بمجرد أن يكون لدى Rust جميع الميزات في مكانها الصحيح) فلن نضطر إلى فعل أكثر من ذلك بكثير لتحقيق ذلك متوافق مع الإصدارات السابقة مع الكود الحالي:

type PinMut<'a, T> = &'a mut Pinned<T>;
type Pin<'a, T> = &'a Pinned<T>;

(أعتقد أنه تم اقتراح تطبيق deref من Pin إلى Pinned ). من المفيد إلقاء نظرة على الأماكن التي يبدو أن هذا لن يعمل فيها. فمثلا:

impl Drop for Pinned<TwoGenerators>

لا يعمل (على الأقل ، ليس بشكل مباشر) مع PinMut . حتى لو افترضنا أن هذا يحل محل Drop لـ TwoGenerators نفسه عند إنشاء النوع Pinned (لست متأكدًا من كيفية عمل ذلك؟) ، فإن Rust لا يزال غير تعرف على استدعاء الإصدار Pinned للمُنشئ لأي حقول تم توقعها ، حيث سيتم الاحتفاظ بالقيمة فقط للحقول. إذا تم دائمًا استخدام الإصدار المثبت من المدمر إذا كان موجودًا ، فهذا مطابق فعليًا لـ PinDrop ، فقط مع بناء جملة غريب ، ولا أرى كيف يكون أفضل.

ومع ذلك ، يميل المرء إلى التفكير في اختيار أداة التدمير عن طريق التحليل المتكرر لما إذا كانت القيمة متجذرة عند Pinned . لاحظ أنه إذا أردنا في أي وقت السماح لكائنات السمات حسب القيمة ، فلا يمكننا بالضرورة الاعتماد على القدرة على تقرير ما إذا كنا سنستخدم الانخفاض Pinned<T> أو انخفاض T في وقت التجميع ؛ أفترض أنك تعتقد أنه يمكن أن يكون هناك إدخال vtable منفصل لإصدار Pinned في مثل هذه الحالات؟ هذه الفكرة هي نوع من الفضول بالنسبة لي. من المؤكد أنه يتطلب دعمًا كبيرًا للمترجم (أكثر بكثير من PinDrop المقترح) ، لكنه قد يكون أجمل بشكل عام من بعض النواحي.

ومع ذلك، إعادة قراءة الموضوع، وأذكر أن هناك مشاكل أخرى: deref التنفيذ لأنواع معلقة حقا لا تعمل بشكل جيد ل Pinned<T> (وأظن أن هذا هو لأن deref التنفيذ على PinMut خطأ منطقيًا بطريقة ما ، ولهذا السبب يستمر في التسبب في مشاكل ، ولكن من الصعب حقًا تبرير خسارته نظرًا لراحته - إلا إذا قمت بعمل مجموعة كاملة من الأنواع دون قيد أو شرط Unpin أي حال). على وجه التحديد ، أجد مثال RefCell مثيرًا للقلق نظرًا لوجود Pinned::deref فهذا يعني أننا لن نكون قادرين على فرض التثبيت ديناميكيًا بالنوع الحالي (لا أعرف إذا كان التخصص كافيا). يشير هذا أيضًا إلى أننا إذا احتفظنا بتنفيذ deref ، فسنضطر في نهاية المطاف إلى تكرار سطح واجهة برمجة التطبيقات مع Pinned كما نفعل مع Pin ؛ وإذا لم نحتفظ بها ، يصبح من الصعب جدًا استخدام Pinned<T> . لا يبدو أيضًا أن Box<Pinned<T>> سيعمل إلا إذا أضفنا ?Move بشكل منفصل عن ?DynSized (كما هو موضح في الموضوع).

قد يكون كل هذا فكرة جيدة ، ولكن في سياق الصدأ الحالي ، يبدو الأمر برمته غير جذاب بالنسبة لي ، خاصة عندما تفكر في أنه لا توجد طرق في Rust الحالية لها حدود ?Move (مما يعني النقص من deref سيؤذي حقًا ، ما لم يكن النوع Unpin وفي هذه الحالة Pinned لا يقدم أي مزايا). أظن أنه يمثل نموذجًا لما يحدث بالفعل عن كثب من خلال جعل تثبيت خاصية ملكية ، والذي بدوره يجعل من الصعب الابتعاد عن قرارات مخصصة مثل PinMut::deref ويؤدي إلى واجهة أكثر إرضاءً (بشكل شخصي على أي حال) ، لكنها تضيف الكثير من آلات اللغة للقيام بذلك ولا يبدو أنك تعتقد أن أيًا منها مفيد بشكل خاص (مقارنة بالأنواع الأصلية غير المنقولة التي تقترحها). لا أعرف أيضًا ما إذا كان ما حصلنا عليه ولم نتمكن من الحصول على توافق كامل مع الإصدارات السابقة (في الغالب ، استدعاء تطبيق إسقاط مختلف اعتمادًا على حالة الدبوس) هو في الواقع مفيد ، حتى لو كان من الممكن القيام به (ربما يمكنك حفظ الفرع في المدمر في بعض الأحيان إذا كنت تعلم أن النوع مثبت؟). لذلك لست متأكدًا مما إذا كان الأمر يستحق تغيير عرض السعر PinMut في هذه المرحلة. لكن ربما أفتقد بعض حالات الاستخدام الملموسة المقنعة حقًا.

الشيء الذي يدور حول Pinned (ما تقترحه) هو أنني أعتقد أنه إذا أردنا تنفيذه (بمجرد أن يكون لدى Rust جميع الميزات في مكانها الصحيح) فلن نضطر إلى فعل أكثر من ذلك بكثير لتحقيق ذلك متوافق مع الإصدارات السابقة مع الكود الحالي:

type PinMut<'a, T> = &'a mut Pinned<T>;
type Pin<'a, T> = &'a Pinned<T>;

بادئ ذي بدء ، سيتطلب Pinned نفسه دعمًا من الحد الأدنى إلى عدم وجود دعم للمترجم ، إذا كان هذا ما تعنيه بـ "الميزات". إذا كنت تقصد تصميم مكتبة ، مثل DynSized والسمات ذات الصلة ، فهذا صحيح ، لكن ...

ما اقترحته لن يكون متوافقًا حقًا مع الإصدارات السابقة ، حيث يمكنك على سبيل المثال محاولة تنفيذ نفس السمة لكل من Pin<'a, T> و &'a T ، الأمر الذي سيتعارض فجأة.

بشكل عام ، هناك فرق كبير في تصميمات API. باستخدام Pin ، يجب أن تأخذ السمات التي يُتوقع أن يتم ضمها بواسطة الأنواع غير المنقولة أساليبها PinMut<Self> ، والوظائف العامة التي ترغب في أخذ إشارات إلى الأنواع غير المنقولة يجب أن تبدو مثل fn foo<T>(p: PinMut<T>) ، من ناحية أخرى ، فإن تصميم Pinned يتجنب الحاجة إلى سمات جديدة في معظم الحالات ، حيث يمكنك تضمين سمات مقابل Pinned<MyStruct> . هكذا:

  1. قد تكون الأنواع غير المنقولة غير متوافقة مع السمات الحالية التي تتطلب أساليبها &self أو &mut self : على سبيل المثال ، لا يمكن للمولدات تضمين Iterator . لذلك سنحتاج إلى مجموعة من السمات الجديدة التي تعادل الصفات الموجودة ولكننا نأخذ PinMut<Self> بدلاً من ذلك. إذا قمنا بتغيير المسار وجعلنا PinMut<T> اسمًا مستعارًا لـ &mut Pinned<T> ، فيمكننا بعد ذلك العودة وإهمال كل هذه السمات المكررة ، لكن هذا سيكون سخيفًا جدًا. من الأفضل عدم الحاجة إلى التكرارات في المقام الأول.

  2. من ناحية أخرى ، من المحتمل أن تأخذ السمات المصممة حديثًا أو الخاصة بالمولد PinMut<Self> كخيار وحيد ، على حساب إضافة ضوضاء للأنواع التي تريد تنفيذ السمات ولكنها ليست ثابتة ولا تحتاج ليتم تثبيتها. (على وجه التحديد ، سيتعين على المتصلين الاتصال بـ PinMut::new للانتقال من &mut self إلى PinMut<Self> ، بافتراض Self: Unpin .) حتى لو أصبح Pin<T> الاسم المستعار لـ &mut Pinned<T> ، لن تكون هناك طريقة للتخلص من هذا الضجيج. وأنواع المستقبل الأصلية غير المنقولة التي أتخيلها ستكون في نفس الموقف مثل الأنواع المنقولة ، وستحتاج دون داع إلى لفها بـ Pinned عندما يتم اعتبارها دائمًا مثبتة.

سأرد على بقية منشورك في منشور ثانٍ.

بخصوص Drop

أنا في حيرة من أمري لما تقوله عن Drop ، لكن بقدر ما تحاول جعله متوافقًا مع PinMut ، لن أفكر في ذلك لأنني لا أعتقد أنه نهج جيد.

أعتقد أن أفضل طريقة هي أنه إذا كان لديك هيكل مثل TwoGenerators ، فلديك خياران:

  1. لا يوجد أداة يدوية Drop إما TwoGenerators أو Pinned<TwoGenerators> ؛
  2. تستخدم Drop مقابل Pinned<TwoGenerators> ؛ في هذه الأثناء ، فإن الماكرو نفسه الذي يمنحك الموصّلين سينشئ ضمناً Drop لـ TwoGenerators نفسه ، والذي يقوم ببساطة بإلقاء الذات على &mut Pinned<TwoGenerators> ويسقط ذلك. (هذا آمن: الثابت المطلوب لنقل &mut T إلى &mut Pinned<T> هو أنك لن تقوم بتحريك T بعد انتهاء الاقتراض ، وفي حالة Drop ، لديك آخر اقتراض سيتم إنشاؤه بهذه القيمة.)

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

لا أفهم سبب رغبتك في الحصول على مدمر منفصل فعلي للحالة المثبتة مقابل الحالة غير المثبتة ، لذلك ليست هناك حاجة للعب الحيل باستخدام vtables للتمييز بينها.

بخصوص RefCell

لا أعتقد أن Pinned::deref يجب أن يكون موجودًا. يجب أن تكون أدوات الوصول الميدانية الآمنة الناتجة عن الماكرو كافية ؛ لا أرى كيف أن هذا "مذهل صعب الاستخدام". إنه أقل جمالًا قليلاً من القدرة على استخدام بناء جملة المجال الأصلي ، ولكن في يوم من الأيام سيتم إصلاح ذلك من خلال الهياكل الأصلية غير المنقولة. على أي حال ، إذا كان من الصعب استخدامه ، تنطبق نفس المشكلة على Pin .

هذا يتجنب المشكلة مع RefCell .

خاصة عندما تفكر في أنه لا توجد طرق في Rust الحالية لها حدود ?Move (مما يعني أن عدم وجود deref سيؤذي حقًا [..])

على العكس من ذلك ، فإن كل شيء له حد ?Sized هو ضمنيًا ?Move .

هذا منطقي لأنه بشكل عام ، من المستحيل أن تفترض الكود الذي يحتوي على ?Sized قابلية التنقل. الاستثناء الوحيد هو الكود غير الآمن الذي يستدعي size_of_val ثم memcpy هذا العدد الكبير من البايت ، وهذا هو السبب في أننا نحتاج إلى الاختراق حيث size_of_val قد يصاب بالذعر من الأنواع غير المنقولة (ويتم إهماله لصالح وظيفة جديدة مع الالتزام الصحيح).

أنا مرتبك قليلاً مما تقوله عن Drop ، ولكن إلى الحد الذي تحاول جعله متوافقًا مع PinMut ، لن أفكر في ذلك لأنني لا أعتقد أن هذا نهج جيد .

كنت أقول أنه إذا كان هناك شيء غير متوافق مع PinMut ، فيجب أن يكون هناك سبب وجيه لذلك. ومع ذلك ، فإن الشيء الذي تقترحه مطابق وظيفيًا لـ PinDrop في كل شيء باستثناء أنك تريد تنفيذه على Pinned<T> (وهو ما لا يعمل في Rust الحالي). أنا شخصياً أعتقد أن Drop يشكل سابقة مشكوك فيها حقًا وهو بالتأكيد غير مرغوب فيه لأسباب لا علاقة لها بالتثبيت ، لذلك لا أعتبر هذا أي نوع من المزايا الجوهرية. على أي حال ، أعتقد أنه يمكن فصل PinDrop الغالب عن باقي اقتراحك.

على أي حال ، إذا كان من الصعب استخدامه ، فإن نفس المشكلة تنطبق على Pin.

بالتأكيد ، وإذا كنا على استعداد للتخلص من PinMut::deref ، فسيتم تكوينه أيضًا بشكل جيد بأنواع مثل RefCell ؛ الفرق هو أنه لا يزال بإمكاننا إيجاد حل مع PinMut مع دعم deref ، والذي يبدو أنه لا يعمل مع Pinned . إذا أردنا التخلص من تنفيذ deref ، أعتقد أنني سأوافق على الأرجح على أن Pinned يوفر ميزة مفيدة.

لكنني لست متأكدًا من أنني أوافق على أن عدم وجود deref هو مجرد مشكلة صغيرة في الممارسة: على سبيل المثال ، هذا يعني أنه في حالته الحالية لا يمكنك فعل أي شيء باستخدام &Pinned<Vec<T>> حيث T: !Unpin ، ونفس الشيء ينطبق بشكل أساسي على كل نوع مكتبة موجود. هذه مشكلة نابعة من الطريقة التي يعمل بها Unpin ، وليس نوع المرجع المحدد لديك. سيتعين على النظام البيئي أن يقرر بشكل جماعي القيام بأشياء على غرار impl Deref for Pinned<Vec<T>> { type Target = Pinned<[T]>; } أو شيء ما ، والذي أعتقد أنني أوافق على أنه سيكون أفضل من impl PinDeref<Vec<T>> إذا كان من الممكن تشغيله ، ولكن هذا في عالم بدون deref . في العالم مع deref ، يمكن لجميع المكتبات تقريبًا أن تفلت من دون أي موصّل مرتبط بالدبابيس على الإطلاق ، ولا يزال لديها دعم نصف لائق لأنواع !Unpin .

على العكس من ذلك ، كل ما له حدود الحجم هو ضمنيًا التحرك.

آه نعم ، هذه نقطة جيدة. لسوء الحظ ، لا تعمل مجموعة كاملة من كود Rust مع الأنواع ذات القيمة !Sized ، لأنها ليست الخيار الافتراضي ، ولكن على الأقل بعضها يعمل. لا أعتقد أن هذه ميزة مقنعة لأن معظم الأشياء التي يمكنني القيام بها على قيم غير بحجم هي استدعاء طرق & أو &mut عليها (على سبيل المثال للشرائح أو كائنات السمات) ، ولا أحد من وهو ما سأكون قادرًا على القيام به بموجب اقتراحك (باستثناء أنواع Unpin ) نظرًا لأنك لا تريد Pinned::deref . ربما يمكن التعامل مع الحالات الشائعة من خلال إنشاء تطبيقات #[derive] أيضًا مثيلات مميزة لـ Pinned<T> أو شيء ما؟

Drop

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

أوافق على أن هذا يمكن فصله ، لكن لا أعتقد أنه مشكوك فيه. على الأقل ... ما قلته صحيح ، إنه نوع من التخصص. إنه ليس تخصصًا حرفيًا لبعض الضمانات الشاملة الأصلية لـ Drop ، لكن الغراء المتساقط الناتج عن المترجم يقوم بما يعادل التخصص عن طريق استدعاء Drop فقط إذا تم تنفيذه. سيبدو تطبيق "userland" بهذا الشكل (متجاهلاً حقيقة أنه لا يمكنك استدعاء drop يدويًا):

trait DropIfImplemented {
    fn maybe_drop(&mut self);
}
impl<T: ?Sized> DropIfImplemented for T {
    default fn maybe_drop(&mut self) {}
}
impl<T: ?Sized + Drop> DropIfImplemented for T {
    fn maybe_drop(&mut self) { self.drop() }
}

لذلك ، أتخيل أن السبب وراء عدم قدرتك على كتابة دروب `` متخصص '' حاليًا هو نفس السبب في أن التخصص نفسه غير سليم حاليًا: عدم الترابط بين المتحولين (الذي يمحو معلمات العمر) والطباعة (وهو ليس كذلك). بمعنى آخر ، إذا كان بإمكاننا كتابة ، على سبيل المثال ، impl Drop for Foo<'static> ، فسيتم استدعاؤه فعليًا لأي Foo<'a> ، وليس فقط Foo<'static> ، لأن codegen يفترض أن النوعين متطابقان.

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

الآن ، لا نريد منع التثبيت على التخصص. ومع ذلك ، فإنني أدعي أن السماح impl Drop for Pinned<MyStruct> - أو بشكل عام ، السماح impl<params> Drop for Pinned<MyStruct<params>> ظل نفس الشروط التي يسمح بها المترجم حاليًا impl<params> Drop for MyStruct<params> - مضمون ليكون مجموعة فرعية من التخصص اسمح ، لذلك إذا قمنا بعمل حالة خاصة لهذا اليوم ، فسوف تختفي في النهاية لتصبح قاعدة أكثر عمومية.

لكن مرة أخرى ، هذا قابل للفصل ؛ إذا كان الناس لا يحبون هذا ، فيمكننا الحصول على سمة منفصلة بدلاً من ذلك.

Unpin

لكنني لست متأكدًا من أنني أوافق على أن عدم امتلاك deref هو مجرد مشكلة صغيرة من الناحية العملية: على سبيل المثال ، هذا يعني أنه في حالته الحالية لا يمكنك فعل أي شيء باستخدام &Pinned<Vec<T>> حيث T: !Unpin ، وينطبق الشيء نفسه على كل نوع مكتبة موجود بشكل أساسي. هذه مشكلة نابعة من الطريقة التي يعمل بها Unpin ، وليس نوع المرجع المحدد الذي لديك.

حسنًا ، دعني أصحح بياني. يجب أن يكون Pinned::deref موجودًا ، ولكن يجب أن يكون مقيدًا بـ Unpin - على الرغم من أنني سأسميها Move .

السبب الوحيد وراء تسبب التضمين Deref لـ PinMut حدوث مشكلات مع RefCell هو أنه (على عكس الضمنية DerefMut ) لا يقتصر على Unpin . والسبب في عدم الالتزام هو الرغبة في السماح للمستخدمين بالحصول على &MyImmovableType ، مما يسمح للأنواع غير المنقولة بتضمين سمات بأساليب تتطلب &self ، ليتم تمريرها إلى وظائف عامة تتطلب &T ، إلخ. هذا مستحيل بشكل أساسي لـ &mut self ، لكنه يعمل في الغالب مع &self لأنه لا يمكنك الخروج منه بـ mem::swap أو mem::replace - هذا ما عدا عند استخدام RefCell . ولكن ، حسب المنطق ، يعد التوافق مع المراجع الحالية ذا قيمة كافية بحيث يجب دعمها ، حتى لو كان التقييد على المراجع غير القابلة للتغيير يبدو تعسفيًا ، حتى لو تسبب في التلاعب.

باستخدام Pinned ، يمكننا دعم كلٍّ من المراجع الثابتة والمتغيرة: ما عليك سوى تضمين سماتك على Pinned<MyStruct> بدلاً من MyStruct . الجانب السلبي هو أنه غير متوافق مع السمات أو الوظائف التي تتطلب &T ولكن بشكل منفصل لها Self: Sized ملزمة ؛ لكنها نادرة نسبيًا ، وغالبًا ما تكون غير مقصودة.

ومن المثير للاهتمام أن Pinned بحد ذاته لا يتطلب حقًا Unpin للوجود على الإطلاق. بعد كل شيء ، لماذا يقوم أي شخص بالفعل بإنشاء &Pinned<Vec<T>> ؟ مع PinMut ، ستتطلب السمات المختلفة PinMut<Self> ، لذلك حتى الإشارات الضمنية لتلك السمات للأنواع المتحركة يجب أن تحصل على PinMut . مع Pinned ، كما قلت ، ستستمر السمات في الحصول على &self أو &mut self ، وستضمّنها مقابل Pinned<MyStruct> . إذا كنت ترغب في تضمين نفس السمة لـ Vec<T> ، فلا يلزم ظهور Pinned في الصورة.

ومع ذلك ، قد يكون أحد المصادر المحتملة هو حقل الوصول إلى الماكرو. اذا كنت تمتلك

struct SomePinnable {
    gen: FakeGenerator,
    also_a_vec: Vec<Foo>,
}

عندئذٍ ، سيولد أبسط تصميم دائمًا موصّلات باستخدام Pinned :

impl Pinned<SomePinnable> {
    fn gen(&self) -> &Pinned<FakeGenerator> { … }
    fn gen_mut(&mut self) -> &mut Pinned<FakeGenerator> { … }
    fn also_a_vec(&self) -> &Pinned<Vec<Foo>> { … }
    fn also_a_vec_mut(&mut self) -> &mut Pinned<Vec<Foo>> { … }
}

... والأمر متروك لك للتعامل مع Pinned إذا كنت لا تريد ذلك. في الواقع ، أعتقد أن هذا جيد ، لأن Unpin / Move يجب أن يكون موجودًا بالفعل ، انظر أدناه. ولكن إذا لم يكن موجودًا ، فسيكون البديل هو وجود طريقة للاشتراك على أساس كل حقل لتلقي مرجع مباشر بدلاً من Pinned واحد. هذا هو ، كنت قد فعلت

    fn also_a_vec(&self) -> &Vec<Foo> { … }
    fn also_a_vec_mut(&mut self) -> &mut Vec<Foo> { … }

سيكون من غير الصحيح أن يكون لديك موصّل Pinned وغير موصّل Pinned ، ولكن يجب أن يكون أي منهما على ما يرام.

… لكن نعم ، نحن بحاجة إلى سمة Move ، وليس فقط لـ Pinned . على سبيل المثال ، سيكون جزءًا من الإصدار الجديد من size_of_val (تصحيح: لن يكون كذلك ، ولكن من المتوقع أن يتحقق الرمز غير الآمن من ذلك قبل محاولة memcpy أنواع عشوائية بناءً على النتيجة من size_of_val ) ؛ في المستقبل مع قيم rvalues ​​غير المحسّنة ، ستكون هي المقيدة (مسترخية من Sized ) لـ ptr::read و mem::swap و mem::replace ؛ إذا حصلنا على &move ، فسيكون ذلك ملزمًا للسماح لك بالخروج من واحد ؛ وشيء مماثل ينطبق على حذف نسخة مضمونة.

لذلك ، طالما أن لدينا السمة ، فلا يوجد سبب لعدم وجود Pinned::derefderef_mut ) مع ربط T: Move .

[تحرير: كما ذكرني الثعبان ، هذا له دلالات مختلفة عن Unpin ، لذا لا تهتم.]

(وبعد ذلك ، تريد الأنواع مثل Vec و Box تضمين Move يدويًا بحيث يتم تطبيقه بغض النظر عما إذا كان نوع العنصر Move .)

حسنًا ، دعني أصحح بياني. يجب أن يكون Pinned :: deref موجودًا ، ولكن يجب أن يكون مقيدًا بـ Unpin - وإن كنت سأسميها Move.

حسنًا ، انتظر. هل هذا ?Move أو Move ؟ الأول يعني أنه لا يمكن إنشاء أنواع !Unpin في كثير من الحالات ؛ هذا الأخير يجعلني أتساءل كيف ، بالضبط ، نشير إلى أنواع مثل Pinned<T> (نظرًا لأن ?DynSized ليس حقًا الحد المناسب لهم). آمل بالتأكيد ألا يكونوا متشابهين - وإلا فإن معاملة Move أنها Unpin مرة أخرى تفعل الشيء المحدد الذي نحاول تجنبه وتجعل النوع ثابتًا لحظة إنشائه.

الجانب السلبي هو أنه غير متوافق مع السمات أو الوظائف التي تأخذ & T ولكن لها ارتباط ذاتي: الحجم بشكل منفصل ؛ لكنها نادرة نسبيًا ، وغالبًا ما تكون غير مقصودة.

هناك جانب سلبي عملي أكثر أهمية ، وهو أن القليل من تلك السمات أو الوظائف تعمل بالفعل مع & مثبتاليوم. يمكن جعلها تعمل معها ولكنها تتطلب عددًا كبيرًا من تطبيقات السمات الإضافية (كما قلت) وربما إصلاحًا كبيرًا للتطبيقات الحالية #[derive] . هذه أيضًا تكلفة يجب دفعها مقابل الأشياء الجديدة أيضًا - سيتعين عليك تنفيذ كل شيء مقابل &Pinned<Self> أيضًا إذا كنت تريد أن تعمل على أنواع !Unpin . هذا وضع أفضل (كثيرًا) للسمات التي تأخذ &mut self من PinMut ، لكن أسوأ من &self ، وهو (أظن) أكثر شيوعًا. ولهذا السبب أقول أعتقد أن هذا هو الحل الأكثر صحة (بمعنى أنه إذا لم يكن لدينا العديد من مكتبات Rust الحالية ، فسيكون الإصدار Pinned أفضل) ولكن ربما لا يكون أكثر قابلية للاستخدام.

مع Pinned ، كما قلت ، ستستمر السمات في أخذ & self أو & mut self ، وستضمّنها لـ Pinned

إعادة تنفيذ كل سمة في سطح واجهة برمجة تطبيقات Vec ، فقط مع Pinned هذه المرة ، لا يبدو رائعًا بالنسبة لي (خاصة وأن بعض السمات لا تعمل معها). أنا متأكد تمامًا إما من تنفيذ Deref بشكل انتقائي على أساس كل حالة على حدة (السماح لـ &Pinned<Vec<T>> بالانتقال إلى &[Pinned<T>] ، على سبيل المثال) ، أو ترك Vec بالكامل Unpin (وعدم السماح بإسقاط الدبوس) ، هو طريقة أكثر عقلانية. على أي حال ، كلاهما عمل أكثر من عدم القيام بأي شيء على الإطلاق ، ويجب تكراره عبر مجموعة كاملة من الأنواع والسمات الموجودة حتى تعمل الأشياء الثابتة معهم.

يمكن أن أكون مقتنعًا بخلاف ذلك - فأنا بالفعل أحب الحل Pinned أكثر كلما فكرت فيه أكثر - لكنني لا أعرف أين يتم تنفيذ كل هذه السمات الجديدة على Pinned<T> الواقع سيأتي من يبدو لي أن الناس لن يكلفوا أنفسهم عناء تنفيذها.

ومن المثير للاهتمام ، أن Pinned نفسها لا تتطلب حقًا وجود Unpin على الإطلاق. بعد كل شيء ، لماذا يقوم أي شخص بالفعل بإنشاء & مثبت

هناك أسباب وجيهة لرغبتك في القيام بذلك (مثل: يحتوي النوع المثبت على Vec فيه). المجموعات المتطفلة ستواجه هذا النوع من السيناريوهات في كثير من الأحيان. أعتقد أن أي اقتراح مبني على فكرة أن الناس لن يرغبوا أبدًا في الحصول على مراجع Pinned للحاويات الموجودة ، أو أنه عليك الاشتراك في عمل Unpin ، من غير المحتمل أن يعمل بشكل جيد. عدم القدرة على الاشتراك بشكل أساسي في النظام البيئي العادي لـ Rust عن طريق إضافة Unpin سيكون أمرًا مدمرًا بشكل لا يصدق (في الواقع ، ستصبح كل حالة استخدام لدي لأنواع غير منقولة أكثر صعوبة بشكل كبير).

مع PinMut ، سوف تأخذ سمات مختلفة PinMut، لذلك حتى التلميحات الخاصة بتلك السمات للأنواع المتحركة يجب أن تتلقى PinMut.

أكيد! الميزة الكبيرة للإصدار Pinned هي أنك لن تحتاج إلى سمات مميزة للمراجع المثبتة القابلة للتغيير. ومع ذلك ، فهو أسوأ أو محايد من PinMut مع deref لكل سيناريو تقريبًا.

سيكون من غير الصحيح أن يكون لديك كل من موصّلات مثبتة وغير مثبتة ، ولكن يجب أن يكون أي منهما على ما يرام في حد ذاته.

يبدو أن الموصّلات اليدوية التي تتطلب رمزًا غير آمن لتطبيقها فكرة سيئة بالنسبة لي ؛ لا أفهم كيف سيسمح مثل هذا الاقتراح بإنشاء موصّل آمن (كيف يمكنك منع شخص ما من توفير موصّلات غير مثبتة دون جعله يكتب unsafe للتأكيد على أنه لن يفعل ذلك؟). ومع ذلك ، كما لاحظت ، فإن استخدام Move (بافتراض أنه يعني في الواقع Unpin ) سيعمل بشكل جيد.

لذلك ، طالما لدينا السمة ، فلا يوجد سبب لعدم تثبيت Pinned :: deref (و deref_mut) مع T: Move bound.

بالتأكيد. أنا أتحدث على وجه التحديد عن أنواع !Unpin هنا. لا تواجه أنواع Unpin أي مشكلة في التركيب مع PinMut أيضًا ، لذا فهي ليست ذات صلة من وجهة نظري. ومع ذلك ، فإن حدود Unpin (أو Move ) على الأدوية الجنسية ليست ممتعة ومن الأفضل أن تكون قادرًا على تجنبها قدر الإمكان. مرة أخرى ، كما قلت سابقًا: Unpin وأيًا كان ما يعنيه !Sized ليست هي نفسها ولا أعتقد أنه يمكنك معاملتها على أنها متماثلة.

... لكن نعم ، نحن بحاجة إلى سمة "الحركة" ، وليس فقط لـ "مثبت". على سبيل المثال ، سيكون جزءًا من حدود الإصدار الجديد من size_of_val ؛ في المستقبل مع قيم rvalues ​​غير المحسّنة ، سيكون الرابط (مسترخى من Sized) لـ ptr :: read و mem :: swap و mem :: replace ؛ إذا حصلنا على أي وقت مضى وتحركنا ، فسيكون لا بد من السماح لك ، حسنًا ، بالخروج من واحد ؛ وشيء مماثل ينطبق على حذف نسخة مضمونة.

أعتقد أن هذا يخلط مرة أخرى بين !Unpin (والذي يشير إلى أن النوع يمكن أن يكون له متغير تثبيت غير افتراضي) و !DynSized -like !Move ؛ لا يمكن أن يكونوا نفس الشيء دون التسبب في سلوك التجميد غير المرغوب فيه.

عفوًا ، أنت على حق تمامًا. لا يمكن أن يكون Unpin هو نفسه Move .

لذلك أعتقد أنني عدت إلى الاعتقاد بأن Unpin وبالتالي لا يجب أن يكون Pinned::deref موجودًا على الإطلاق ، وبدلاً من ذلك يجب علينا تجنب أي مواقف (مثل مع الماكرو المولّد للوصلة) حيث احصل على نوع مثل &Pinned<MovableType> . لكن ربما هناك حجة مفادها أنه يجب أن توجد كصفة منفصلة.

إعادة تنفيذ كل سمة في سطح واجهة برمجة تطبيقات Vec ، فقط بـ Pinned هذه المرة ، لا يبدو رائعًا بالنسبة لي (خاصة وأن بعض السمات لا تعمل معها). أنا متأكد تمامًا إما من تنفيذ Deref بشكل انتقائي على أساس كل حالة على حدة (السماح لـ &Pinned<Vec<T>> بالانتقال إلى &[Pinned<T>] ، على سبيل المثال) ، أو ترك Vec بالكامل Unpin (وعدم السماح بإسقاط الدبوس) ، هو طريقة أكثر عقلانية.

نعم ، بالتأكيد لم أقصد اقتراح إعادة تنفيذ كامل سطح واجهة برمجة تطبيقات Vec أو أي شيء من هذا القبيل.

أوافق على أنه من الجيد عدم السماح بـ "إسقاط الدبوس" على Vec ، نظرًا لأن &Pinned<Vec<T>> يبدو وكأنه ثابت غريب - يجب أن يُسمح لك بتحريك Vec بدون إبطال دبوس المحتويات.

كبديل ، ماذا عن السماح بالتحويل من Vec<T> إلى Vec<Pinned<T>> ، والذي قد يحتوي على معظم Vec API ولكن مع حذف الطرق التي يمكن أن تسبب إعادة التخصيص؟ أي ، قم بتغيير تعريف Vec من struct Vec<T> إلى struct Vec<T: ?Sized + ActuallySized> ، لبعض الأسماء الأقل سخافة ، حيث يصبح Sized أساسًا اسمًا مستعارًا لـ ActuallySized + Move ؛ ثم أضف Sized مرتبطًا بالطرق التي يمكن أن تؤدي إلى إعادة التخصيص ، وطريقة لإجراء التحويل.

سيكون من الضروري أيضًا تغيير الحد على نوع عنصر نوع الشريحة المضمنة من Sized إلى ActuallySized . التفكير في هذا يذكرني بالإحراج في تغيير Sized ليعني شيئًا آخر غير Sized ، لكن من ناحية أخرى ، لا يزال صحيحًا أن الغالبية العظمى من Sized bounds في التعليمات البرمجية الحالية تتطلب بطبيعتها Move . إن الحاجة إلى معرفة size_of::<T>() للفهرسة إلى شريحة هو استثناء قليلاً ...

أكيد! الميزة الكبيرة لإصدار Pinned هي أنك لن تحتاج إلى سمات مميزة للمراجع المثبتة القابلة للتغيير. ومع ذلك ، فهو أسوأ أو محايد من PinMut مع deref لكل سيناريو آخر تقريبًا.

كما أن لديها ميزة تجنب التعارض مع RefCell ، باعتراف الجميع بتكلفة التعارض مع الأشياء الأخرى ( Sized bounds).

يبدو أن الموصّلات اليدوية التي تتطلب رمزًا غير آمن لتطبيقها فكرة سيئة بالنسبة لي ؛ لا أفهم كيف سيسمح مثل هذا الاقتراح بإنشاء موصّل آمن (كيف يمكنك منع شخص ما من توفير موصّلات غير مثبتة دون جعله يكتب بطريقة غير آمنة للتأكيد على أنه لن يفعل ذلك؟).

لأن الموصِّلات مُضمَّنة على Pinned<MyStruct> ، وليس MyStruct مباشرةً. إذا كان لديك &mut MyStruct ، يمكنك دائمًا الوصول يدويًا إلى حقل للحصول على &mut MyField ، لكنك لا تزال في حالة متحركة. إذا كان لديك &mut Pinned<MyStruct> ، فلا يمكنك الحصول على &mut MyStruct (بافتراض أن MyStruct هو !Unpin أو Unpin غير موجود) ، لذلك عليك استخدام ملحق للوصول إلى الحقول. يأخذ الموصل &mut Pinned<MyStruct> (أي يأخذ &mut self ويتم تضمينه في Pinned<MyStruct> ) ويمنحك إما &mut Pinned<MyField> أو &mut MyField ، حسب الخيار الذي اخترته. ولكن لا يمكنك الحصول إلا على نوع واحد من الملحقات أو النوع الآخر ، حيث أن العامل الثابت هو أنه يجب ألا تكون قادرًا على الحصول على &mut Pinned<MyField> ، الكتابة إليه ، تحرير الاقتراض ، ثم الحصول على &mut MyField (وانقله).

هناك أسباب وجيهة لرغبتك في القيام بذلك (مثل: يحتوي النوع المثبت على Vec فيه). المجموعات المتطفلة ستواجه هذا النوع من السيناريوهات في كثير من الأحيان. أعتقد أن أي اقتراح مبني على فكرة أن الناس لن يرغبوا أبدًا في الحصول على مراجع Pinned للحاويات الحالية ، أو أنه عليك الاشتراك في عمل Unpin ، من غير المحتمل أن يعمل بشكل جيد. عدم القدرة على الاشتراك بشكل أساسي في النظام البيئي العادي لـ Rust عن طريق إضافة Unpin سيكون أمرًا مدمرًا بشكل لا يصدق (في الواقع ، ستصبح كل حالة استخدام لدي لأنواع غير منقولة أكثر صعوبة بشكل ملحوظ).

لا أفهم تمامًا ما تقصده هنا ، ولكن قد يكون ذلك بسبب رد فعلك على خطئي wrt Unpin مقابل Move :)

الآن بعد أن تم تصحيحي ... إذا كان Unpin موجودًا ، فيجب أن يتضمنه Vec . لكن بافتراض عدم وجودها ، ما هي بالضبط السيناريوهات التي تشير إليها؟

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

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

comex

كبديل ، ماذا عن السماح بالتحويل من Vecإلى Vec> ، ما الذي قد يحتوي على معظم واجهة برمجة تطبيقات Vec ولكنه يتجاهل الأساليب التي يمكن أن تسبب إعادة التخصيص؟

لان...

أي تغيير تعريف Vec من الهيكل الحالي Vecلبناء Vec، لبعض الأسماء الأقل سخافة ، حيث يصبح Sized في الأساس اسمًا مستعارًا لـ ActuallySized + Move ؛ ثم أضف حجمًا مرتبطًا بالطرق التي يمكن أن تتسبب في إعادة التوزيع ، وطريقة لإجراء التحويل.

... هذا يبدو معقدًا حقًا ولا يفعل الشيء الذي تريده في الواقع (وهو الحصول على Vec<T> عادي من &mut Pinned<Vec<T>> أو أي شيء آخر). إنه لأمر رائع أنه يتيح لك تثبيت متجه بعد الحقيقة ، لذلك تحصل على نظير لطيف إلى Box<Pinned<T>> ، لكن هذا أمر متعامد ؛ إنه مجرد توضيح آخر لحقيقة أن التثبيت ربما يكون ملكية من النوع المملوك صحيحًا. أعتقد أن كل شيء يتعلق بـ Unpin يكاد يكون غير مرتبط تمامًا بمسألة كيفية إنشاء نوع المرجع.

الآن بعد أن تم تصحيحي ... إذا كان Unpin موجودًا ، فيجب على Vec تضمينه. لكن بافتراض عدم وجودها ، ما هي بالضبط السيناريوهات التي تشير إليها؟

سأرد فقط على هذا لأنني أعتقد أنه سيوضح وجهة نظري: لا يمكنني حتى تغيير حقل i32 من خلال PinMut بدون Unpin . في مرحلة ما إذا كنت تريد أن تفعل أي شيء بهيكلك ، فغالبًا ما يتعين عليك تحريك شيء بداخله (ما لم يكن غير قابل للتغيير تمامًا). تبدو حاجة الأشخاص إلى تنفيذ أدوات الوصول الميدانية بشكل صريح على Pinned<MyType> أمرًا مزعجًا حقًا ، خاصةً إذا كان الحقل المعني دائمًا آمنًا للتنقل. يبدو أيضًا أن هذا النهج سيكون محيرًا حقًا عند استخدامه مع النوع المدمج Pinned حيث أن التوقعات القانونية تختلف بطريقة ما باختلاف الحقل بطريقة لا تعتمد على نوع الحقل ، وهو شيء تم رفضه بالفعل في Rust عندما تمت إزالة حقول mut (و IMO ، إذا أردنا إضافة مثل هذا التعليق التوضيحي unsafe هو خيار أفضل ، نظرًا لأن الحقول غير الآمنة تعد سلاحًا ضخمًا في الممارسة). نظرًا لأن الأنواع المثبتة المضمنة هي الطريقة الوحيدة تقريبًا لجعل التعدادات المثبتة سهلة الاستخدام ، فأنا أهتم بقدرتها على أن تكون متسقة إلى حد ما مع بقية اللغة.

ولكن الأهم من ذلك...

أعتقد أن هذا سيكون مشكلة إذا كنت تريد بنية عامة مع حقل قد يحتوي على Vec ، أو قد يحتوي على نوع ثابت ، اعتمادًا على معلمة النوع

هذا إلى حد كبير حالة الاستخدام القاتلة لـ Unpin ، و (بالنسبة لي) حقيقة أنه يبدو أنه يعمل بالفعل أمر رائع للغاية ويثبت صحة نموذج التثبيت بالكامل (إلى الحد الذي أعتقد أنه حتى لو كنا نبدأ من قبل Rust 1.0 ، ربما نريد الاحتفاظ بـ Unpin كما هو). تعد المعلمات العامة التي تعيش بشكل مضمّن في الهيكل هي الوقت الوحيد الذي يجب أن تلتزم فيه بـ Unpin إذا كانت الخطط الحالية (لجعل جميع الأنواع المشابهة للمراجع الآمنة تقريبًا تنفذ Unpin دون قيد أو شرط) اذهب عبر.

ولكن ما لا أحصل عليه حقًا هو: لماذا تريد إزالة Unpin ؟ القضاء عليه لا يشتري لك شيئًا في الأساس ؛ كل الأشياء الرائعة التي تحصل عليها من Pinned أكثر من PinMut (أو العكس) لا علاقة لها إلى حد كبير بوجودها. إضافته تجعلك توافقًا سهلاً مع بقية نظام Rust البيئي. FWIW و Unpin والتنفيذ غير المشروط لـ deref على Pin غير مرتبطين إلى حد كبير ببعضهما البعض أيضًا ، باستثناء أنه بدون Unpin كل الأنواع تشعر بنفس الشيء الألم الذي تفعله أنواع !Unpin (بمعنى أنه من المحتمل أن يجعل التنفيذ غير المشروط deref أكثر فائدة). لا يسعني إلا أن أشعر أنني أفتقد شيئًا ما.

هل المجالات الأخرى في الهيكل التي يتم إسقاطها تعتبر متغيرات محلية في هذا السياق؟ هذا في الحقيقة غير واضح بالنسبة لي من أي وثائق تواجه المستخدم

عادل بما فيه الكفاية ، لقد فتحت https://github.com/rust-lang/rust/issues/50765 لتتبع ذلك.


تضمين التغريدة

على وجه التحديد ، أجد مثال RefCell مزعجًا للغاية لأنه في وجود Pinned :: deref ، فهذا يعني أننا لن نكون قادرين حتى على فرض التثبيت ديناميكيًا بالنوع الحالي (لا أعرف ما إذا كان التخصص سيكون كافيًا). يشير هذا أيضًا إلى أننا إذا حافظنا على تطبيق deref ، فسنضطر إلى إنهاء نسخ سطح API تقريبًا بنفس القدر مع Pinned كما نفعل مع Pin ؛ وإذا لم نحتفظ به ، فقد تم تثبيتهيصبح من الصعب جدًا استخدامه.

الحل لـ RefCell هو توفير طرق إضافية borrow_pin و borrow_pin_mut (مع أخذ Pin<RefCell<T>> ) ، وتتبع الحالة المثبتة لداخل RefCell في وقت التشغيل. يجب أن يعمل هذا لكل من PinMut و Pinned . إذن ، هل حجتك هنا أن Pinned لا تساعد؟ لا ينبغي أن يجعل الأمور أكثر سوءًا مقابل RefCell أيضًا.

ولكن بعد ذلك تكتب

الفرق هو أنه لا يزال بإمكاننا إيجاد حل مع PinMut مع دعم deref ، والذي يبدو أنه لا يعمل مع Pinned

ولا أعرف ما الذي تشير إليه ، فلماذا لا يعمل هذا مع Pinned ؟

comex

لا أعتقد أن Pinned :: deref يجب أن توجد. يجب أن تكون أدوات الوصول الميدانية الآمنة الناتجة عن الماكرو كافية ؛ لا أرى كيف أن هذا "مذهل صعب الاستخدام".

لا أرى الاتصال بين deref وموصلي الحقول. لكنني أيضًا لا أرى كيف يصبح deref أكثر إشكالية مع Pinned<T> ، لذلك أعتقد أنني سأنتظر إجابة لسؤالي أعلاه أولاً.

تمامًا مثل pythonesque ، أعتقد أن تتبع الحالة المثبتة على النوع خلف المرجع ("على النوع المملوك") أكثر صحة بشكل أساسي. ومع ذلك ، فأنا أشك في أنه يمكن تحويله فعليًا إلى واجهة برمجة تطبيقات عامة أكثر راحة ، لا سيما بالنظر إلى قيود العمل مع نظام Rust البيئي الحالي.

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

pythonesque ، واو ، شكرًا جزيلاً على الملخص الشامل! سعيد لسماع أن هناك RFC قيد التنفيذ. :)

إنه حفظ لاستدعاء دالة تأخذ مرجعًا متغيرًا لقيمة مثبتة طالما أن هذه الوظيفة لا تستخدم شيئًا مثل mem::swap أو mem::replace . بسبب ذلك ، يبدو من الطبيعي أن تستخدم هذه الوظائف قيمة Unpin من أن يكون كل مشتق قابل للتغيير من قيمة Pin إلى Unpin غير آمن.

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

لذا كان لدي بعض الأفكار:

  1. بشكل أساسي ، يوفر Drop الامتياز نفسه مثل Unpin - يمكنك الحصول على &mut لشيء كان سابقًا في PinMut . و Drop آمن ، مما يعني أن Unpin يجب أن يكون آمنًا (هذا mem::forget و leapocalypse من جديد).
  2. هذا جيد ، لأنه يعني أن أشياء مثل واجهات برمجة التطبيقات الحالية المستندة إلى العقود الآجلة ، والتي لا تتعامل مع مولدات إلغاء التثبيت ، كلها آمنة بنسبة 100٪ للتنفيذ حتى لو كانت تأخذ self: PinMut<Self> (لا unsafe impl Unpin ).
  3. هل صوت API إذا كان Unpin آمنًا؟ الإجابة هي نعم: طالما أن المولدات لا تطبق Unpin ، وليس من الآمن تثبيت المشروع على نوع !Unpin ، فكل شيء آمن.
  4. لكن هذا يعني أن إسقاطات الدبوس غير آمنة! ليست مثالية.
  5. تكون إسقاطات الدبوس آمنة إذا لم يطبق Self Unpin أو Drop [تحرير: صحيح أم خطأ؟] هل يمكننا أتمتة هذا الشيك؟

لدي بعض الأفكار عن بديل أكثر المدعومة من لغة إلى هذا API مكتبة، والتي تنطوي على اسقاط Unpin تماما، وبدلا من التقليب قطبية - على Pin سمة أن تختار في الحصول على هذه الضمانات، بدلا من الانسحاب . لكن هذا سيتطلب دعمًا لغويًا كبيرًا ، في حين أن التطبيق الحالي يعتمد بالكامل على المكتبات. سأقوم بعمل منشور آخر بعد أن فكرت فيه أكثر.


ملاحظة أخرى لأنني ما زلت أنسى:

تعتمد سلامة إسقاط الدبوس على النوع الذاتي فقط ، وليس نوع الحقل ، لأن نوع الحقل يجب أن يضمن سلامة واجهة برمجة التطبيقات العامة الخاصة به ، وهو أمر غير مشروط. لذلك فهو ليس شيكًا متكررًا - إذا لم ينقل Self أي شيء من Pin ، فإن إسقاط الدبوس إلى حقل أي نوع آمن.

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

تضمين التغريدة

بشكل أساسي ، يوفر Drop نفس الامتياز مثل Unpin - يمكنك الحصول على & mut لشيء كان سابقًا في PinMut. و Drop آمن ، مما يعني أن Unpin يجب أن يكون آمنًا (هذا هو mem :: forget و leakpocalypse من جديد).

لا أرى أي صلة بتسريب العالم لكني أوافق على خلاف ذلك. السبب الوحيد الذي جعلني (كنت؟) مترددًا بعض الشيء هو أنه طالما أنه فقط Drop ، فقد شعرت أن هذا الأمر أشبه بحالة زاوية لا يجب على الكثير من الناس الاهتمام بها. لست متأكدًا مما إذا كانت هذه ميزة أم لا. وعلى أي حال ، لا يؤدي استخدام آمن Unpin زيادة الاتساق هنا فقط و "يحل" مشكلة Drop خلال عدم جعلها حالة خاصة (بدلاً من ذلك يمكننا التفكير في كل impl Drop أنها تأتي مع impl Unpin ضمنيًا ؛ مما تقوله أيضًا يجعل من السهل استخدامه على جانب Future للأشياء. لذلك ، يبدو أنه فوز شامل.

pythonesque ما لم Unpin لا يسبب أي مشكلات جديدة للمجموعات المتطفلة أيضًا ، أليس كذلك؟ إذا عملوا على الرغم من الأمان Drop ، فلا يزال يتعين عليهم العمل.


تضمين التغريدة

تُعد إسقاطات الدبوس آمنة إذا لم يقم Self بتنفيذ Unpin أو Drop [تحرير: صحيح أم خطأ؟] هل يمكننا أتمتة هذا الفحص؟

لقد ذكرت في البداية نوع الحقل هنا وكنت على وشك أن أسأل لماذا تعتقد أن هذا مناسب. الآن أراك تقوم بتحرير المنشور. :) أوافق على أن هذا يتعلق بالنوع Self فقط. في ما يلي أكرر إلى حد كبير حجتك بعباراتي.

السؤال الأساسي هو: كيف يختار Self ثابت التثبيت الخاص به؟ افتراضيًا ، نفترض (حتى لو كان الرمز غير الآمن موجودًا!) أن ثابت التثبيت هو بالضبط نفس الثابت المملوك ، أي أن الثابت مستقل عن الموقع وهذا النوع لا يقوم بالتثبيت. طالما لا يمكنني فعل أي شيء باستخدام PinMut<T> بخلاف تحويله إلى &mut T ، فهذا افتراض آمن.

لتمكين الإسقاطات الميدانية ، يجب أن يكون ثابت التثبيت بدلاً من ذلك "جميع الحقول الخاصة بي مثبتة في النوع الخاص بها". يبرر هذا الثابت بسهولة إسقاطات التثبيت ، بغض النظر عن أنواع الحقول (أي يمكنهم اختيار أي ثابت تثبيت يريدون). بالطبع لا يتوافق هذا المتغير مع تحويل PinMut<T> إلى &mut T ، لذا من الأفضل أن نتأكد من أن هذه الأنواع ليست Drop أو Unpin .

لا أرى أي صلة بتسريب العالم لكني أوافق على خلاف ذلك.

مجرد تشبيه - Unpin هو إسقاط كما mem :: forget هو دورات Rc. mem :: forget تم تمييزه في الأصل بأنه غير آمن ، ولكن لم يكن هناك مبرر لذلك. (وتم تقديم نفس الحجة القائلة بأن دورات Rc هي حالة حافة ضد وضع علامة mem :: forget safe.)

نسخ لصق (روحانيًا) من Discord ، أود حقًا أن أرى دليلًا على أننا لم نقلب المشكلة فقط: جعل Unpin آمنًا للتنفيذ ، من خلال جعل موصّلات الدبوس الهيكلية غير آمنة للتنفيذ (سيكون هذا أيضًا صحيحًا مع أي سمة غير آمنة تم تقديمها ، أليس كذلك؟ لا يزال يتعين عليك كتابة رمز غير آمن). هذا يزعجني لأن الغالبية العظمى من الوقت يكونون آمنين تمامًا - بشكل أساسي ، طالما لا يوجد ضمانة Unpin صريحة للنوع ، تمامًا كما أننا دائمًا بأمان إذا لم يكن هناك Drop الضمني للنوع. مع الخطة الحالية ، نحن نطلب شيئًا أقوى بكثير - يجب أن يكون هناك ضمني صريح! Unpin للنوع - والذي سيكون صحيحًا للعديد من الأنواع الأقل (هذا هو كل ما يمكننا فعله في المكتبة).

لسوء الحظ ، ليس لدي أي فكرة عن كيفية أو ما إذا كان المترجم يمكنه التحقق مما إذا كان هناك ضمني Unpin يدوي لنوع معين أم لا ، على عكس "لديه أي ضمني" ، ولست متأكدًا مما إذا كان لديه تفاعلات سيئة مع التخصص. إذا كانت لدينا طريقة محددة لإجراء هذا الفحص ، بحيث لا يضطر منشئ النوع إلى كتابة أي رمز غير آمن للحصول على تثبيت هيكلي ، فسأكون أكثر سعادة مع impl Unpin لكونه آمنًا ، على ما أعتقد ... هل هذا شيء يبدو ممكنًا؟

لدي سؤال بسيط أحاول أن أفهمه الآن. في الأدوية العامة ، هل سيكون إلغاء التثبيت بمثابة ارتباط ضمني مثل الحجم ما لم تكن واجهة برمجة التطبيقات الخاصة بك لجميع المعلمات العامة؟

يجب أن يكون ذلك صحيحًا من أجل vecللاستمرار في أن تكون آمنًا.

سيتم إلغاء التثبيت بشكل ضمني مثل الحجم

لا.

يجب أن يكون ذلك صحيحًا حتى يظل Vec آمنًا.

لماذا تظن ذلك؟

الشيء المزعج الذي ضربتهMajor breakfast اليوم: PinMut::get_mut يمنحك &mut مع عمر PinMut الأصلي ، لكن لا توجد طريقة آمنة لفعل الشيء نفسه ، منذ DerefMut يمنحك PinMut . ربما يجب أن نجعل get_mut الخزنة ، وإضافة get_mut_unchecked ؟

تقصد مثل هذا؟

fn get_mut(this: PinMut<'a, T>) -> &'a mut T where T : Unpin

نعم ، يجب أن نحصل على ذلك.

تضمين التغريدة

الطريقة من صندوق العقود الآجلة التي أود استخدام هذا فيها تبدو كالتالي:

fn next(&mut self) -> Option<&'a mut F> {
    self.0.next().map(|f| unsafe { PinMut::get_mut(f) })
}

اضطررت إلى استخدام كتلة غير آمنة على الرغم من أن F Unpin مرتبط بـ PinMut .

نعم هذا مجرد إغفال في واجهة برمجة التطبيقات.

هل تريد إعداد العلاقات العامة أم ينبغي عليّ؟

أستطيع فعلها. هل نريد تسميتها get_mut و get_mut_unchecked ؟

سأضيف خزنة map وأعيد تسمية الخزانة الحالية إلى map_unchecked أيضًا. بشكل أساسي من أجل الاتساق. ثم جميع الوظائف غير الآمنة لـ PinMut end في _unchecked ولها مكافئ آمن

هل نريد تسميته get_mut و get_mut_unchecked؟

يبدو معقولا.

سأضيف خزنة map

احذر هناك. ماذا تريد أن تبدو؟ السبب في أن الخريطة غير آمنة هو أننا لا نعرف كيف نصنع خريطة آمنة.

احذر هناك. ماذا تريد أن تبدو؟

أنت على حق. يجب أن يتطلب الأمر Unpin ملزمًا بقيمة الإرجاع.

خطرت لي فكرة للتو: ماذا عن PinArc ؟ يمكننا استخدام مثل هذا الشيء في الضمانة BiLock (https://github.com/rust-lang-nursery/futures-rs/pull/1044) في صندوق العقود الآجلة.

Major breakfast PinArcPinRc ، إلخ) يعتمد على Pin (الإصدار بخلاف Mut ). سأكون مستعدًا لإضافته ، لكنني لم أكن متأكدًا مما إذا كان هناك إجماع حول الضمانات التي سيقدمها بالضبط.

لست متأكدًا بعد الآن مما إذا كانت إضافة PinArc تضيف شيئًا ^^ ' Arc لا يسمح بالفعل "بالخروج من المحتوى المستعير"

Major breakfast يمكنك الانتقال من Arc باستخدام Arc::try_unwrap . سيكون تغييرًا غير متوافق مع الإصدارات السابقة لإزالة ذلك أو تقديم T: Unpin ملزم.

مرحبا!
لقد كنت أفكر قليلاً في كيفية الحصول على وصلات دبوس للعمل بأمان. بقدر ما أفهم ، تحدث المشكلة المتعلقة بوصلات دبوس _ فقط_ عندما يكون لديك المجموعة T: !Unpin + Drop . تحقيقًا لهذه الغاية ، رأيت بعض المناقشات تحاول منع احتمال وجود مثل هذا النوع T - على سبيل المثال جعل السمات !Unpin و Drop حصرية بشكل متبادل بطرق مختلفة ، ولكن هناك لم تكن طريقة واضحة للقيام بذلك دون كسر التوافق مع الإصدارات السابقة. وعند النظر إلى هذه المشكلة عن كثب، يمكن أن نحصل من يمكنهم الدخول دبوس آمنة دون منع نوع من كونها !Unpin و Drop ، انها مجرد أن مثل هذا النوع لا يمكن وضع في Pin<T> . إن منع حدوث _that_ هو شيء أعتقد أنه أكثر قابلية للتنفيذ ، وأكثر من ذلك بروح كيفية عمل سمة Unpin أي حال.

في الوقت الحالي ، لدي "حل" لمنع أي نوع من الدخول بأمان إلى Pin<T> على حد سواء !Unpin و Drop . إنه نوع من يتطلب ميزات ليست لدينا في الصدأ حتى الآن ، وهي مشكلة ، لكنني آمل أن تكون البداية. على أي حال ، هذا هو الرمز ...

/// This is an empty trait, it is used solely in the bounds of `Pin`
unsafe trait Pinnable {}

/// Add the extra trait bounds here, and add it to the various impls of Pin as well
struct Pin<T: Pinnable> {
    ...
}

/// Then we impl Pinnable for all the types we want to be able to put into pins
unsafe impl<T: Unpin> Pinnable for T {}
unsafe impl<T: !Unpin + !Drop> Pinnable for T {}

كلا النوعين Unpin والأنواع التي هي !Unpin ولكن أيضًا !Drop ، ليس لديهم مشاكل (على حد علمي) مع موصّلات الدبوس. هذا لا يكسر أي كود موجود يستخدم Drop ، إنه يحد فقط ما يمكن وضعه في هيكل Pin . في الحالة غير المحتملة * التي يحتاج فيها شخص ما إلى أن يكون النوع !Unpin و Drop (ويمكن وضعه بالفعل داخل Pin ) ، فسيكون قادرًا على unsafe impl Pinnable لنوعهم.

* ليس لدي خبرة في Pin ، لكنني آمل أن معظم الحالات التي يحتاج فيها شخص ما إلى impl Drop لنوع !Unpin ليست بسبب الشيء الذي يجب إسقاطه باعتباره !Unpin بطبيعته ، ولكنه ناتج عن وجود بنية بها حقول Drop ، و !Unpin حقول. في هذه الحالة ، يمكن فصل الحقول Drop إلى هيكلها الخاص الذي لا يحتوي على حقول !Unpin . يمكنني الخوض في مزيد من التفاصيل حول ذلك إذا لزم الأمر ، لأنني أشعر أن هذا التفسير كان مخادعًا ، لكن لم يكن القصد منه أن يكون الجزء الرئيسي للتعليق ، لذا سأتركه قصيرًا في الوقت الحالي.

بقدر ما أفهم ، تحدث المشكلة مع موصّلات الدبوس فقط عندما يكون لديك مجموعة T:! Unpin + Drop.

إنها أكثر من "أو" من "و".

تشير موصّلات الدبوس إلى تفسير "هيكلي" للتثبيت: عند تثبيت T ، تكون جميع الحقول كذلك. impl Unpin و Drop آمنين لأنهم يفترضون أن النوع لا يهتم بالتثبيت على الإطلاق - T يتم تثبيته أو لا يحدث فرقًا ؛ على وجه الخصوص ، حقول T غير مثبتة في كلتا الحالتين.

ومع ذلك ، فقد حصلت على هذا الحق لاحقًا عندما قلت إن الأنواع !Unpin + !Drop هي الأنواع التي يمكن إضافة الملحقات إليها بأمان. (نوع ما زالت تحتاج الى أن يكون حريصا على عدم القيام بأي شيء خاطئ في مدونتها غير آمنة، ولكن Unpin إعلان Drop هما طرق آمنة للخروج من يمكنهم الدخول تعلق).

بالنظر إلى هذه المشكلة عن كثب ، يمكننا الحصول على موصّلات Pin آمنة دون منع وجود أي نوع! قم بإلغاء التثبيت والإفلات ، إنه فقط لا يمكن وضع مثل هذا النوع في Pin.

أنا لا أتبع. لماذا تعتقد أن هذا كافٍ؟ وإذا كان الأمر كذلك ، فلماذا نهتم بالموصلات المثبتة إذا كان النوع لا يمكن تثبيته ...؟

إنها أكثر من "أو" من "و".

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

حسب فهمي (الحالي) ، فإن موصّلات الدبوس من النوع Unpin سليمة تمامًا ، بغض النظر عن السقوط ، لأن Pin<T> فعال &mut T أي حال. أيضًا ، موصّلات الدبوس على نوع !Drop سليمة تمامًا ، حتى بالنسبة لنوع !Unpin ، لأن drop هي الوظيفة الوحيدة التي يمكن أن تحصل على &mut self على النوع !Unpin بمجرد إدخاله في Pin ، لذلك لن يتمكن أي شيء من إخراج الأشياء.

إذا أخطأت في ذلك ، فيرجى إبلاغي بذلك. ولكن إذا لم يكن الأمر كذلك ، فستكون الأنواع Unpin + Drop أو !Unpin + !Drop جيدة تمامًا مع موصّلات الدبوس ، وأنواع المشاكل الوحيدة ستكون تلك التي هي !Unpin + Drop .

تضمين التغريدة

أيضًا ، موصّلات الدبوس على نوع! الإسقاط صوتي تمامًا ، حتى بالنسبة لنوع إلغاء التثبيت ، لأن Drop هو الوظيفة الوحيدة التي يمكن أن تحصل على & mut self على! Unpin النوع بمجرد دخوله إلى Pin ، لذلك لن يكون أي شيء قادرًا على ذلك نقل الأشياء.

إذا كتبت struct Foo(InnerNotUnpin); impl Unpin for Foo {} فليس من الصحيح الانتقال من PinMut<Foo> إلى PinMut<InnerNotUnpin> . من أجل أن يكون الإسقاط سليمًا ، يجب عليك (أ) منع إشارات الإسقاط و (ب) التأكد من أن الهيكل هو Unpin فقط عندما يكون الحقل Unpin .

(أ) يجب فقط منع الإفلات على الأشياء التي تكون !Unpin صحيحة؟
(ب) يجب التعامل معه من خلال حقيقة أن تنفيذ Unpin غير آمن - تأكد من إمكانية إطلاق النار على نفسك من خلال الادعاء بشكل غير آمن أنه يمكن إلغاء تثبيت النوع عندما يتعذر عليه فعلاً - ولكن من الممكن تسميته شيء مثل Sync عندما لا يكون الأمر كذلك وينتهي به الأمر بسلوك غير سليم بهذه الطريقة

(أ) يجب فقط منع الإفلات على الأشياء!

نعم ولكن هذا مستحيل لأسباب التوافق مع الإصدارات السابقة.

وهذا يصل إلى وجهة نظري - منع الإفلات يعني ضمناً الأشياء! إلغاء التثبيت مستحيل لأسباب التوافق مع الإصدارات السابقة. لكننا لسنا بحاجة إلى منع السقوط الضمني على الأشياء! علامة! Unpin على نوع ما ليس لها معنى ولا تقدم أي وعود حتى يكون هذا النوع داخل Pin ، لذا فإن الضمانة المنسدلة على! Unpin type سليمة تمامًا _ طالما أن هذا النوع ليس داخل Pin_. ما أقترحه هو تغيير رقم التعريف الشخصي من Pin<T> إلى Pin<T: Pinnable> حيث تعمل السمة Pinnable كحارس بوابة لمنع الأنواع التي تكون Drop + !Unpin (أو أكثر بشكل عام ، الأنواع التي تمثل فيها موصّلات الدبوس مشكلة) من وضعها داخل Pin .

في هذه المرحلة ، أنت تجعل السمة Unpin عديمة الفائدة تمامًا ، أليس كذلك؟ أيضًا ، ما زلت لا تستطيع التعبير عن الحدود الضرورية مع الصدأ الحالي. إذا استطعنا أن نقول " Unpin و Drop غير متوافقين" ، فلن تكون هناك مشكلة. يبدو أنك بحاجة إلى شيء مشابه لـ Pinnable . لا أرى كيف يساعد هذا.

سمة Unpin - من تلقاء نفسها - "عديمة الفائدة" بالفعل. تعني الصفة _ فقط_ شيئًا ما بمجرد تثبيت النوع ، يمكن نقل النوع قبل أن ينتقل إلى الدبوس.

لقد اجتمعنا للتو مع أننا مستعدون لتحقيق الاستقرار فيها بدون تغييرات كبيرة في واجهة برمجة التطبيقات.

حل الأسئلة المعلقة

إيماني حول pin APIs هو أن:

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

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

مشكلة Drop

إحدى المشكلات التي لم نكتشفها إلا بعد دمج RFC كانت مشكلة السمة Drop . الطريقة Drop::drop لها واجهة برمجة تطبيقات تأخذ نفسها &mut self . يعد هذا أساسًا "إزالة تثبيت" Self ، وهو انتهاك للضمانات التي من المفترض أن توفرها الأنواع المثبتة.

لحسن الحظ ، لا يؤثر هذا على سلامة أنواع "المولد غير المتحرك" الأساسية التي نحاول عرضها باستخدام الدبوس: فهم لا يقومون بتطبيق أداة التدمير ، لذا فإن تنفيذها !Unpin يعني ذلك في الواقع .

ومع ذلك ، ما يعنيه هذا هو أنه بالنسبة للأنواع التي تحددها بنفسك ، فمن الآمن بالضرورة "فك التثبيت" ، كل ما عليك فعله هو تنفيذ Drop . ما يعنيه هذا هو أن Unpin كونك unsafe trait لم يكن يشتري لنا أي أمان إضافي: تنفيذ Drop هو تنفيذ Unpin بقدر ما تكون السلامة المعنية.

لهذا السبب ، جعلنا سمة Unpin سمة آمنة يجب تنفيذها.

إسقاط دبوس

يتمثل القيد الرئيسي لواجهة برمجة التطبيقات الحالية في أنه لا يمكن تحديد ما إذا كان من الآمن إجراء إسقاط دبوس عندما لا يقوم الحقل بتطبيق Unpin . الإسقاط دبوس هو:

بالنظر إلى النوع T مع حقل a بالنوع U ، يمكنني بأمان "مشروع" من PinMut<'a, T> إلى PinMut<'a, U> خلال الوصول الحقل a .

أو في الكود:

struct Foo {
    bar: Bar,
}

impl Foo {
    fn bar(self: PinMut<'_, Foo>) -> PinMut<'_, Bar> {
        unsafe { PinMut::map_unchecked(self, |foo| &mut foo.bar) }
    }
}

اعتبارًا من الآن ، ما لم يكن نوع الحقل يستخدم Unpin فليس لدينا ل rustc __ لإثبات_ أن هذا آمن. أي أن تنفيذ هذا النوع من طرق الإسقاط يتطلب حاليًا رمزًا غير آمن. لحسن الحظ ، يمكنك إثبات أنها آمنة. إذا كان نوع الحقل لا يطبق Unpin ، فهذا آمن فقط إذا كانت كل هذه الحجوزات:

  1. نوع الذاتي لا يطبق Unpin ، وإلا فقط بشروط الأدوات Unpin عندما يقوم نوع الحقل.
  2. لا يقوم النوع الذاتي بتطبيق Drop ، وإلا فإن المدمر للنوع الذاتي لا يقوم أبدًا بنقل أي شيء خارج هذا الحقل.

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

نظرًا لمشكلة Drop ، وهي سمة مستقرة ، كانت هذه المشكلة موجودة مسبقًا لأي قرار يمكننا اتخاذه بشأن هذه المشكلة ، ولا توجد طريقة بدون تغييرات اللغة لجعل إسقاط الدبوس آمنًا تلقائيًا. يومًا ما ، قد نجري هذه التغييرات ، لكننا لن نمنع تثبيت واجهات برمجة التطبيقات هذه على ذلك.

تحديد الضمان "رقم التعريف الشخصي"

في وقت متأخر من عملية RFC ، أدرك cramertj أنه مع وجود امتداد طفيف يتجاوز ما نحتاج إليه للمولدات التثبيت لتغليف ضمانات القوائم المتطفلة بأمان. كتب رالف وصفا لهذا الامتداد هنا .

بشكل أساسي ، ضمان التثبيت هو:

إذا كان لديك PinMut<T> ، و T لا يطبق Unpin ، فلن يتم نقل T ولن يتم نقل الذاكرة التي تدعم T إبطال مفعولها حتى بعد تشغيل أداة التدمير مقابل T .

هذا ليس تمامًا مثل "حرية التسريب" - لا يزال من الممكن تسريب T على سبيل المثال من خلال الوقوع خلف دورة Rc ، ولكن حتى إذا تم تسريبها ، فهذا يعني أن الذاكرة لن يتم إبطالها أبدًا (حتى البرنامج تنتهي) ، لأن المدمر لن يعمل أبدًا.

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

  1. تم اكتشاف واجهات برمجة تطبيقات أخرى ، أكثر راحة ، مثل هذا الماكرو ، مما يلغي الجانب السلبي.
  2. الاتجاه الصعودي كبير جدًا! قد تكون هذه الأنواع من القوائم المتطفلة ذات تأثير كبير على مكتبات مثل جوزفين التي تدمج Rust مع لغات جمع القمامة.
  3. لم يكن رالف متأكدًا من أن بعض واجهات برمجة التطبيقات هذه كانت سليمة حقًا لتبدأ بها (خاصة تلك التي تستند إلى "الاقتراض الدائم").

إعادة تنظيم API

ومع ذلك ، أريد إجراء تغيير نهائي مقترح واحد على واجهة برمجة التطبيقات ، وهو نقل الأشياء. بمراجعة واجهات برمجة التطبيقات الموجودة ، أدركت Ralf & I أنه لا يوجد مكان جيد لوصف الضمانات عالية المستوى والثوابت المتعلقة بـ Drop وما إلى ذلك. لذلك ، أقترح

  1. قمنا بإنشاء وحدة جديدة std::pin . ستوفر مستندات الوحدة نظرة عامة عالية المستوى على التثبيت ، والضمانات التي توفرها ، ومن المتوقع أن يدعم المستخدمون الثابتون للأجزاء غير الآمنة.
  2. قمنا بنقل PinMut و PinBox من std::mem و std::boxed إلى الوحدة النمطية pin . نترك Unpin في الوحدة النمطية std::marker ، مع سمات العلامة الأخرى.

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

إضافة تطبيقات Unpin

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

ومع ذلك ، نعتقد أنه كقاعدة عامة:

  1. لا ينبغي أبدًا التعامل مع إسقاط الدبوس من خلال مؤشر على أنه آمن (انظر مربع الملاحظات أدناه).
  2. لا ينبغي أبدًا التعامل مع إسقاط الدبوس من خلال قابلية التغيير الداخلية على أنه آمن.

بشكل عام ، تقوم الأنواع التي تقوم بأحد هذه الأشياء بعمل شيء ما (مثل إعادة تخصيص مخزن الدعم كما في Vec ) مما يجعل إسقاط الدبوس غير مقبول. لهذا السبب ، نعتقد أنه سيكون من المناسب إضافة تطبيق غير مشروط Unpin لجميع أنواع الأمراض المنقولة جنسياً التي تحتوي على معلمة عامة إما خلف مؤشر أو داخل UnsafeCell ؛ وهذا يشمل كل من std::collections على سبيل المثال.

لم أقم بالتحقيق عن كثب ، لكنني أعتقد أنه سيكون كافياً لمعظم هذه الأنواع إذا أضفنا هذه الضمانات فقط:

impl<T: ?Sized> Unpin for *const T { }
impl<T: ?Sized> Unpin for *mut T { }
impl<T: ?Sized> Unpin for UnsafeCell<T> { }

هناك مشكلة واحدة: من الناحية الفنية ، نعتقد أن عرض الدبوس من خلال Box يمكن جعله آمنًا تقنيًا: أي الانتقال من PinMut<Box<T>> إلى PinMut<T> يمكن القيام به بأمان. هذا لأن Box هو مؤشر مملوك بالكامل ولا يعيد تخصيص نفسه أبدًا.

من الممكن أننا نريد توفير فجوة بحيث يتم تطبيق Unpin فقط بشروط مقابل Box<T> عندما T: Unpin . ومع ذلك ، فإن رأيي الشخصي هو أنه يجب أن يكون من الممكن التفكير في التثبيت على أنه كتلة من الذاكرة تم تثبيتها في مكانها ، وبالتالي يجب أن يكون كل الإسقاط من خلال توجيه المؤشر غير آمن.

يمكننا تأخير هذا القرار دون منع الاستقرار وإضافة هذه الضمانات بمرور الوقت.

استنتاج

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

تضمينrfcbot fcp

أقترح تثبيت واجهات برمجة تطبيقات الدبوس الحالية بعد بعض عمليات إعادة التنظيم الصغيرة. يمكنك قراءة ملخص أطول في رسالتي السابقة . TL ؛ DR في رأيي هو أن API الحالي هو:

  • صوت
  • جيدة كما تحصل دون تغيير اللغة
  • متوافق مع التغييرات اللغوية التي تجعله أفضل

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

أقترح بعض التغييرات قبل أن نثبت الأشياء بالفعل:

  1. أعد تنظيم PinMut و PinBox ضمن وحدة جديدة std::pin ، والتي ستتضمن وثائق عالية المستوى بخصوص الثوابت وضمانات التثبيت بشكل عام.
  2. إضافة تطبيقات غير مشروطة Unpin للأنواع الموجودة في الأمراض المنقولة جنسياً والتي تحتوي على معلمات عامة إما خلف اتجاه المؤشر أو داخل UnsafeCell ؛ المبرر لماذا هذه التطبيقات مناسبة في الملخص الأطول.

اقترح عضو الفريق withoutboats دمج هذا. الخطوة التالية هي المراجعة بواسطة باقي الفرق الموسومة:

  • [] Kimundi
  • []SimonSapin
  • []alexcrichton
  • [] @ dtolnay
  • []sfackler
  • [x] @ بدون قوارب

لا مخاوف مدرجة حاليا.

بمجرد موافقة غالبية المراجعين (وعدم اعتراضهم) ، سيدخل هذا فترة التعليق النهائية. إذا اكتشفت مشكلة كبيرة لم تُثار في أي وقت في هذه العملية ، فيرجى التحدث!

راجع هذا المستند للحصول على معلومات حول الأوامر التي يمكن أن يقدمها لي أعضاء الفريق.

rfcbot fcp إلغاء

أقوم بوضع علامات على lang أيضًا بسبب القرارات الأساسية الثابتة التي كان علينا اتخاذها ، لذلك أقوم بإلغاء وإعادة تشغيل FCP

تم إلغاء اقتراح

rfcbot fcp merge انظر رسالة الدمج السابقة والملخص الطويل ، آسف!

اقترح عضو الفريق withoutboats دمج هذا. الخطوة التالية هي المراجعة بواسطة باقي الفرق الموسومة:

  • [x] Kimundi
  • []SimonSapin
  • [x]alexcrichton
  • []aturon
  • [x]cramertj
  • [x] dtolnay
  • [x]eddyb
  • [] @ joshtriplett
  • []nikomatsakis
  • [x]nrc
  • []pnkfelix
  • [x] @ scottmcm
  • []sfackler
  • [x] @ بدون قوارب

اهتمامات:

بمجرد موافقة غالبية المراجعين (وعدم اعتراضهم) ، سيدخل هذا فترة التعليق النهائية. إذا اكتشفت مشكلة كبيرة لم تُثار في أي وقت في هذه العملية ، فيرجى التحدث!

راجع هذا المستند للحصول على معلومات حول الأوامر التي يمكن أن يقدمها لي أعضاء الفريق.

يسعدني أن أرى بعض الثقة في أن هذا سليم. ومع ذلك ، من الصعب التعامل مع كل ما تغير منذ RFC الأصلي ؛ هل يمكن تحديث نص RFC ليعكس التصميم المقترح للتثبيت؟ (بشكل عام ، هذه مشكلة يواجهها الكثير من طلبات التعليقات (RFC) ، حيث يتغيرون كثيرًا خلال فترة التنفيذ بحيث تكون محاولة فهمها من خلال قراءة طلب التعليقات أمرًا غير مجدٍ. يجب أن نجد طريقة للقيام بعمل أفضل هناك.)

طلبات التعليقات ليست مواصفات. قراءة RFC ليست بديلاً عن التوثيق الفعلي.

bstrie التغيير الكبير من RFC ( Unpin آمن) مغطى في منشور الملخص الخاص بي. على المدى الطويل ، يجب أن يكتسب الأشخاص فهمًا لواجهة برمجة التطبيقات المثبتة من خلال قراءة المستندات في std::pin (مانع التثبيت) ، وليس من خلال البحث عن RFC الأصلي.

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

bstrie هو إجراء قياسي لدينا لاقتراح FCP قبل التوثيق. لقد كتبت تعليقًا موجزًا ​​شاملاً للغاية حول حالة اللعب والذي يجب أن يوفر معلومات كافية لأي شخص لم يتابع مشكلة التتبع ، بالإضافة إلى تعليق أقصر للأشخاص الذين لا يريدون هذا السياق كثيرًا

للتوضيح أكثر قليلاً ، FCP هو السؤال "هل نريد تثبيت هذا؟" إذا تبين أن هذه الإجابة "لا" ، فقد تم إهدار مجموعة من الأعمال المتعلقة بالمستندات. لا يعني إكمال FCP أن الميزة تصبح مستقرة على الفور ؛ هذا يعني أن القرار قد تم اتخاذه لتثبيته ، ولذا فقد حان الوقت الآن للقيام بالعمل المطلوب للقيام بذلك ؛ يتضمن عمل المترجم والتوثيق.

في 2 آب (أغسطس) 2018 ، الساعة 12:56 ظهرًا ، كتب موقع boat [email protected] :

bstrie هو إجراء قياسي لدينا لاقتراح FCP قبل التوثيق. لقد كتبت تعليقًا موجزًا ​​شاملاً للغاية حول حالة اللعب والذي يجب أن يوفر معلومات كافية لأي شخص لم يتابع مشكلة التتبع

-
أنت تتلقى هذا لأنك علقت.
قم بالرد على هذه الرسالة الإلكترونية مباشرةً ، أو اعرضها على GitHub ، أو قم بكتم صوت الموضوع.

تضمين التغريدة

لا ينبغي أبدًا التعامل مع إسقاط الدبوس من خلال مؤشر على أنه آمن (انظر مربع الملاحظات أدناه).
لا ينبغي أبدًا التعامل مع إسقاط الدبوس من خلال قابلية التغيير الداخلية على أنه آمن.

هل يمكنك توضيح هذه النقطة قليلاً؟ هل تشير تحديدًا إلى الأنواع الموجودة في المكتبة القياسية ؟ على غرار Box ، هناك أنواع مثل Mutex يمكن أن تقوم بالتثبيت نظرًا لأنها تمتلك محتوياتها بشكل فريد (على الرغم من أنها قد تكون خارج النطاق). على الأقل خارج المكتبة القياسية ، أفترض أننا نرغب في القدرة على الحصول على أساسيات التزامن التي تؤدي إلى PinRef<InPlaceMux<T>> إلى PinMut<T> عند استدعاء .lock() ، والذي يبدو مثل حالة "الإسقاط من خلال التحور الداخلي".

cramertj يبدو الإسقاط في Mutex أمرًا خطيرًا ؛ يمكن للمرء استخدام التحويل الآمن إلى &Mutex ثم Mutex::lock للحصول على &mut ثم نقل الأشياء.

تحرير: أوه ، أنت تقصد دائمًا Mutex . مثل ، PinMutex . نعم ، هذا سيعمل. التحور الداخلي أمر جيد إذا لم تقم أبدًا بتسليم &mut . لكن يبدو من غير المحتمل حاليًا أن يكون لدينا مثل هذه الهياكل البيانات في libstd.

ولكن نعم ، من أجل الدقة الكاملة ، يجب تعديل بيان withoutboats ليقول إن "إسقاط الدبوس من خلال قابلية التغيير الداخلية يكون آمنًا فقط إذا كان النوع دائمًا دبابيس ، على سبيل المثال ، لا يتم &mut ."

RalfJung بالضبط ، أجل.

لكن يبدو من غير المحتمل حاليًا أن يكون لدينا مثل هذه الهياكل البيانات في libstd.

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

cramertj ، إنها بالتأكيد ليست قاعدة

أشعر بخيبة أمل من قرار تثبيت هذا. لست قلقًا بشأن ما يحدث على المدى القصير ، لأنه بدون دعم المترجم ، فإن أي تصميم محتمل سيبدو قليلًا. ومع ذلك ، على المدى الطويل ، إذا اكتسب Rust أنواعًا غير منقولة أصلية ، فلن يشعروا بأنهم من الدرجة الأولى حقًا ما لم يتم استخدامها مع طرق السمات التي تقبل &self و &mut self . نتيجة لذلك ، يجب أن يكون التثبيت خاصية من نوع المرجع ، وليس نوع المرجع. لا يزال من الممكن أن يحدث ذلك ، بالطبع ، في التصميم المستقبلي للأنواع غير المنقولة الأصلية. ولكن سينتج عن ذلك أن يصبح Pin<T> زائدًا عن الحاجة - ليس فقط ، على سبيل المثال ، اسمًا مستعارًا مهملاً لبعض الأشخاص الأصليين &pin T ، ولكن غير ضروري تمامًا. في المقابل ، ستحتاج سمات مثل Future (وربما العديد من الميزات الأخرى) التي تقبل Pin<Self> إلى التجديد أو الإهمال ، الأمر الذي سيتطلب فترة انتقالية فوضوية.

فوضوي ، ليس مستحيلاً. ولكن مع وجود خطر مفرط في التشاؤم ، أشعر بالقلق من أن الرغبة في تجنب مثل هذا الانتقال ستؤدي إلى تحيز القرارات المستقبلية نحو اعتماد &pin بدلاً من ذلك كأساس للأنواع الثابتة غير المنقولة - والتي ، في رأيي ، ستترك منهم من الدرجة الثانية بشكل دائم. وما زلت أعتقد أنه من بين التصميمات قصيرة المدى ، كان من الممكن أن يكون نهج &Pinned<T> ممكنًا ، مع تجنب مخاوف التوافق إلى الأمام هذه والحصول على مزايا أخرى أيضًا (على سبيل المثال ، تجنب المشكلة تمامًا مع قابلية التغيير الداخلية)

اوه حسنا. كما هي ، ما زلت أتطلع إلى استخدام Pin ، لكنني كنت أفضل إذا بقيت على crates.io بدلاً من الدخول إلى المكتبة القياسية.

أوافق على أنه من السابق لأوانه تثبيت هذا الأمر. في الوقت الحالي ، يعد الأمر محرجًا إلى حد ما في بعض المواقف ، على سبيل المثال عند الوصول إلى حقول من !Unpin اكتب داخل طريقة تسمى PinMut<Self> . أعتقد أن تغييرات withoutboats ستحسن الوضع ، لكنني لست متأكدًا. ألن يستحق الانتظار حتى يتم تطبيق هذه التغييرات واختبارها على الأقل لبعض الوقت؟

Thomasdezeeuw @ ما هي التغييرات التي تعتقد أنها ستحسن الوضع التغييرات التي اقترحتها لا يبدو أنها تتعلق بهذا الإزعاج.

لا توجد طريقة لكتابة حل عام - أي لإضافة طريقة إلى الأمراض المنقولة جنسياً - ولكن هذا الاختلاف في الماكرو عن العقود الآجلة آمن:

macro_rules! get_mut {
    ($f:tt: $t:ty) => (
        fn $f<'a>(
            self: &'a mut std::mem::PinMut<Self>
        ) -> &'a mut $t
            where $t: Unpin
        {
            unsafe {
                 &mut std::mem::PinMut::get_mut_unchecked(self.reborrow()).$f
            }
        }
    )
}

struct Foo {
    bar: Bar,
}

impl Foo {
     get_mut!(bar: Bar);
}

withoutboats هذا آمن لأنه يوفر فقط الوصول إلى بيانات Unpin ، أليس كذلك؟

RalfJung بالضبط! حيث يجعله سطر الشرط آمنًا

withoutboats هذا ماكرو لطيف ولكنه يعمل فقط لأنواع Unpin .

ولكن أليس من الممكن إضافة طريقة / ماكرو يتطلب PinMut<Self> ويعيد PinMut<Self.field> ، ويفضل أن يكون في كود آمن. قد أكون مخطئًا ولكن تفكيري هو أنه إذا كان المتصل ضامنًا أن Self تم تثبيته ، فهل إذا كان Self.field ، أليس كذلك؟ يجب أن يعمل هذا أيضًا مع الأنواع التي لا تطبق Unpin .

Thomasdezeeuw تمت مناقشة هذه المشكلة في الملخص الخاص بي تحت قسم "Pin projection" ؛ ليس من الآمن الإسقاط على حقول !Unpin ما لم تضمن أيضًا أن Self لا يقوم بأشياء أخرى معينة ، والتي لا يمكننا إنشاء شيك لها. يمكننا فقط أن نضمن تلقائيًا القدرة على المشروع على الحقول التي تنفذ Unpin (لأن هذا آمن دائمًا ، ويمكننا التحقق منه بعبارة where ).

withoutboats لقد قرأت الملخص ، لكن ربما أسيء فهمي. ولكن إذا كان النوع T هو !Unpin فلن يقوم PinMut<T> بتنفيذ DerefMut for T ، وبالتالي لن يكون نقل القيمة ممكنًا بدون رمز غير آمن. لا تزال لديك مشكلة مع السمة Drop ، لكن هذا أكبر من مجرد الوصول إلى حقل النوع. لذلك لا أرى ما يجعل التعيين من PinMut<T> إلى PinMut<T.field> غير آمن ، حتى إذا كان T.field هو !Unpin .

Thomasdezeeuw في Futures-rs لدينا مواقف يكون لدينا فيها نوع داخل PinMut ، لكن أحد حقولها لا يعتبر مثبتًا

الجانب السلبي للماكرو من خلال withoutboats هو أنه يمكنه منح حق الوصول إلى حقل واحد فقط في كل مرة لأنه يحتوي على مرجع قابل للتغيير إلى self . لا تحتوي عمليات الوصول إلى حقل الهيكل على هذا القيد.

أعتقد أن اتخاذ قرار بشأن تثبيت API هو القرار الصحيح. ومع ذلك ، أقترح تنفيذ الوحدة النمطية core::pin المقترحة أولاً.

لا تزال لديك مشكلة في سمة Drop ، ولكن هذا أكبر من مجرد الوصول إلى حقل النوع.

كلا ليس كذلك. Drop جيد حقًا طالما لم تقم بتعيين الحقول . هذا هو السبب في أن رسم الخرائط الميدانية غير آمن.

لا تزال لديك مشكلة في سمة Drop ، ولكن هذا أكبر من مجرد الوصول إلى حقل النوع. لذلك لا أرى ما الذي يجعل التعيين من PinMutإلى PinMutغير آمن ، حتى لو كان T.field!

للتوضيح ، لا يمكننا إنشاء دليل تلقائيًا على أن T: !Unpin أيضًا. ولكن حتى إذا لم يكن الأمر كذلك ، فإليك مثالاً على كيفية استخدام الأداة Drop :

struct Foo {
    field: UnpinFuture,
}

impl Foo {
     fn field(self: PinMut<Self>) -> PinMut<UnpinFuture> { ... }

     fn poll_field(self: PinMut<Self>, ctx: &mut Context) {
         self.field().poll(ctx);
     }
}

impl Drop {
    fn drop(&mut self) {
        // ...
        let moved_field = mem::replace(&mut self.field, UnpinFuture::new());

        // polling after move! violated the guarantee!
        PinBox::new(moved_field).as_pin().poll();
    }
}

كما يقول RalfJung ، هذه هي بالضبط مشكلة Drop - إذا لم تقم بأي إسقاطات دبوسية لحقول !Unpin ، فإن كل ما تفعله في Drop جيد.

الجانب السلبي للماكرو من خلال withoutboats هو أنه يمكنه منح حق الوصول إلى حقل واحد فقط في كل مرة لأنه يحتوي على مرجع قابل للتغيير إلى self. لا تحتوي عمليات الوصول إلى حقل الهيكل على هذا القيد.

يمكنك كتابة الطريقة التي تتيح الوصول إلى حقول متعددة ، ولكن عليك القيام بذلك لكل مجموعة تهتم بها.

بدافع الفضول ، هل هناك حالات استخدام ملموسة أخرى إلى جانب العقود الآجلة ، أو على الأقل شيء آخر يحفز الرغبة في تثبيت هذه الحالات الآن (على عكس ما يحدث لاحقًا مع المزيد من الخبرة)؟

يبدو أنه لا يوجد نقاش إضافي هنا فيما يتعلق بالنقاط التي أثارتهاcomex. لقد جعلوني أشعر بالفضول ، لأنني لا أتذكر فكرة جعل pin خاصية من النوع بدلاً من المرجع. هل نوقش هذا في مكان آخر بالفعل؟ ليس من السهل متابعة كل شيء من "الخارج" ، فأنا أبذل قصارى جهدي ؛-)

في المقابل ، سمات مثل Future (وربما العديد من السمات الأخرى) التي تقبل Pinسوف تحتاج إلى تجديد أو إهمال ، الأمر الذي يتطلب فترة انتقالية فوضوية.

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

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

yasammez لقد تمكنت من التقاط مناقشة حول هنا

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

لا أعتقد أنه من المهم إثبات أمان الكثير من هذه التعليمات البرمجية غير الآمنة بشكل تافه. إن مجرد وجود الشفرة غير الآمنة يقلل من قدرة المرء على تدقيق المزيد من التعليمات البرمجية غير الآمنة الحقيقية .

tikue في المدى القريب ، يمكنك إما:

  1. استخدم ماكرو مثل unsafe_pinned الماكرو الذي طورته مكتبة العقود الآجلة ، مما حد من نفسك إلى واحد غير آمن لكل استطلاع داخلي ، والذي يجب عليك التحقق منه مقابل القيود الموضحة في الملخص الطويل (لا يمكنك أيضًا استخدام ماكرو فقط اكتب الموصّل يدويًا ، فهو أيضًا unsafe ).
  2. تتطلب حقولك تنفيذ Unpin ، مما يجعل مثل هذه التوقعات آمنة تمامًا ولا تتطلب رمزًا غير آمن ، على حساب كومة تخصيص أي !Unpin للعقود الآجلة.

yasammez تمت مناقشته بإسهاب في هذا الموضوع: https://internals.rust-lang.org/t/can-pin-own-its-referent-instead-of-borrowing-mutably/7238/20

withoutboats أفهم الخيارات ولكن أجد صعوبة في تذوقها. سيؤدي طلب Unpin إلى تقييد التوافق مع مجموعة كاملة من العقود الآجلة ، مثل أي شيء يقترضه ذاتيًا ، مثل أي fn غير متزامن باستخدام القنوات. والتلوث غير الآمن مصدر قلق حقيقي.

من الناحية العملية ، حاولت ترحيل مكتبة إلى العقود الآجلة 0.3 ووجدتها صعوبة لهذه الأسباب.

tikue هذا متوافق مع توفير الشيك لك بطريقة آلية. لم نقم بتصميم أو تنفيذ ذلك بالكامل حتى الآن ، لكن لا شيء هنا يمنع حدوثه.

ومع ذلك ، فأنا لا أفهم حقًا هذا القلق:

والتلوث غير الآمن مصدر قلق حقيقي.

غالبًا ما يتم ترك "مشكلة غير آمنة" عند هذا المستوى الذي أشعر به - مجرد حظر شامل على unsafe .

بالنسبة لمعظم العقود الآجلة المتداخلة ، يجب أن يكون من السهل جدًا تحديد ما إذا كان يمكنك استخدام الماكرو unsafe_pinned! بأمان. عادة ما تكون قائمة التحقق على النحو التالي:

  1. لم أقم بتطبيق Unpin يدويًا لمستقبلي ، وبدلاً من ذلك أعتمد فقط على ضمنية السمة التلقائية.
  2. لم أقم بتطبيق Drop يدويًا لمستقبلي ، لأنه ليس لدي أداة تدمير مخصصة.
  3. المجال الذي أريد عرض المشروع عليه خاص.

في هذه الحالة: أنت جيد! unafe_pinned آمنة للاستخدام.

إذا لم تنفذ يدويا Unpin أو Drop ، عليك أن تفكر فعلا في ذلك، ولكن حتى في هذه الحالة ليس بالضرورة مثل هذه المشكلة الصعبة وهي:

  1. إذا قمت بتطبيق Unpin ، فسيكون ذلك مقيدًا بالمستقبل الذي أجرده أكثر من Unpin .
  2. إذا قمت بتطبيق Drop ، فلن أحرك الحقل المستقبلي أبدًا أثناء التدمير.

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

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

ماذا عن PinMut::replace ؟

impl<'a, T> PinMut<'a, T> {
  pub fn replace(&mut self, x: T) { unsafe {
    ptr::drop_in_place(self.inner as *mut T);
    ptr::write(self.inner as *mut T, x);
  } }
}

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

أعتقد أنه آمن بالتأكيد مقابل PinBox ، لأنني أحرص على استدعاء المدمر "في مكانه" ومن ثم هذا مجرد إعادة استخدام الذاكرة. ومع ذلك ، فأنا قلق بعض الشيء بشأن PinMut بشأن انخفاض القيمة مبكرًا ، أي قبل انتهاء عمرها. لا يبدو واضحًا بالنسبة لي أن هذا سيكون دائمًا على ما يرام ، لكنني أيضًا لا أستطيع التوصل إلى مثال مضاد. cramertjwithoutboats أي أفكار؟

(أود أن أضيف هذا إلى rfcbot إذا كان بإمكاني ...)

rfcbot قلق استبدال

RalfJung قد أكون مخطئًا هنا ، لكن ، ألا يمكن أن يسقط هذا الرمز المزدوج إذا كان T في حالة ذعر؟

RalfJung لدينا بالفعل PinMut::set .

تضمين التغريدة نعم حسنًا ، كان من الممكن أن أكون أكثر دقة في التحقق من ذلك.

آسف للضوضاء ، قلقي قد تم حله.

rfcbot حل استبدال

هذا رمز حقيقي أكتبه الآن للعقود الآجلة 0.1 => 0.3 التوافق مع tokio_timer::Deadline . تحتاج إلى إنشاء PinMut<Future01CompatExt<Delay>> ، حيث Delay هو حقل Deadline .

        let mut compat;
        let mut delay = unsafe {
            let me = PinMut::get_mut_unchecked(self);
            compat = Future01CompatExt::compat(&mut me.delay);
            PinMut::new_unchecked(&mut compat)
        };

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

أعتقد أن هناك متسعًا كبيرًا بين الحظر الشامل على المواد غير الآمنة وبين هذا القانون. على سبيل المثال ، map_unchecked مقيد جدًا للمساعدة في هذا الرمز لأنه يتطلب إعادة مرجع ، بينما يتطلب هذا الرمز إرجاع Future01CompatExt<&mut Delay> .

تحرير: مصدر آخر من عدم الراحة هو أنه لا يمكنك الاقتراض بشكل متبادل في حراس المباريات ، لذلك PinMutلا يمكن العمل مع كود مثل هذا:

`` صدأ
match self.poll_listener (cx)؟ {
// اعتاد أن يكون إذا self.open_connections> 0
استطلاع :: جاهز (بلا) إذا self.open_connections ()> 0 => {
^ ^ ^ ^ استعار بشكل متبادل في نمط الحرس
عودة الاستطلاع :: معلق؛
}
استطلاع :: جاهز (بلا) => {
عودة الاستطلاع :: جاهز (لا شيء) ؛
}
}
""

تعليقك حول map_unchecked في موضعه الصحيح ، فربما يكون هناك توقيع أكثر عمومية يمكن أن يسمح أيضًا بإعادة الموقتات حول المؤشرات.

هل هناك إرشادات حول الوقت الآمن للحصول على مرجع &mut من حقل PinMut<Type> ؟ أعتقد أنه طالما أن Type لا يفترض أبدًا أن الحقل مُثبت ، فلا بأس؟ هل يجب أن تكون خاصة؟

نعم ، إما أن يقوم الحقل بتنفيذ Unpin أو أنك لم تنشئ مطلقًا PinMut للحقل.

rfcbot التباين

أنا أكتب مبلغًا لائقًا من رمز يعتمد على PinMut وأحد الأشياء التي حدثت كثيرًا هو أنني أمتلك عملية لا تعتمد على التثبيت ، ولكنها تتطلب مبلغًا PInMut بدلاً من &mut كطريقة لقول "أعدك بأنني لن أخرج من هذه الذاكرة". ومع ذلك ، فإن هذا يفرض على جميع المستخدمين الحصول على قيمة مثبتة ، وهو أمر غير ضروري. لقد فكرت في الأمر مرة أخرى ولم أتمكن من ابتكار واجهة برمجة تطبيقات جيدة لها ، ولكن سيكون من الرائع أن تكون هناك طريقة لفصل "أحتاج إلى تثبيت حجتي" عن "يمكنني العمل باستخدام ملف مثبت جدال".

أعتقد أن "التباين" مصطلح خاطئ هناك ؛ ما تريده هو مُنشِئات الأنواع المرتبطة للتجريد عبر أنواع المراجع المختلفة: د

RalfJung نعم ، هذا صحيح. ومع ذلك ، أود أن أوافق على نوع مثل يمكن أن يأتي من PinMut أو من & mut ولكن لا يمكن استخدامه إلا مثل PinMut ، وأنا أعلم أن هذا لا يتسع بشكل جيد. أعتقد أن هذا أكثر قابلية للتحقيق من أنواع_الذات_التعسفية العامة: ابتسامة: ربما يكون من الأفضل أن تختتم بعبارة "نحن واثقون تمامًا من أننا قادرون على أداء التوقيعات المستقبلية مثل هذا":

impl MyType {
    fn foo(self: impl MutableRef<Self>, ...) { ... }
}

rfcbot قلق api-refactor

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

عندما كنت أعمل على مكدس الشبكة لـ nt-rs ، قيل لي أن المراجع المثبتة ستساعد في "رقصة الملكية" التي أقوم بحلها حاليًا باستخدام fold كما هو موضح هنا (tx.send move tx into مستقبل Send<<type of tx>> ، مما يجعل من الصعب تكرار البيانات الواردة لإرسال الحزم.). بأي طريقة يمكن أن يساعد التثبيت في هذا النوع من الأشياء؟

Redrield send() يأخذ &mut نفسه في العقود الآجلة 0.3.

withoutboats أنا حريص على تجربة واجهة برمجة التطبيقات الجديدة هذه في العقود الآجلة!

نظرًا لأن واجهة برمجة التطبيقات الجديدة تستخدم معرّفات مختلفة ، أعتقد أنه يجب أن يكون من الممكن توفير كل من واجهات برمجة التطبيقات في وقت واحد. أعتقد أنه سيكون من الأفضل أن تكون هناك فترة انتقالية قصيرة تتوفر خلالها كلتا واجهات برمجة التطبيقات مما يتيح لنا تجربة وإعداد العلاقات العامة ونقل واجهة برمجة التطبيقات المستقبلية في libcore إلى النمط الجديد.

Major breakfast يبدو أننا يمكن أن نطلق على type PinMut<T> = Pin<&mut T>; ونقدم عددًا من نفس الأساليب ، مما يقلل من الحجم الكبير والعاجلة للكسر.

تضمين التغريدة

لذلك مع واجهة برمجة التطبيقات الجديدة:

  1. Pin<&T> و Pin<&mut T> لهما ثابت "التثبيت القياسي" ، حيث القيمة وراءهما:
    1.أ. ليس &T / &mut T صالحًا بعد الآن ، إلا إذا كان Unpin
    1. ب. لن يتم استخدامه باعتباره Pin من عنوان ذاكرة آخر.
    1. ج. سيتم إسقاطها قبل إبطال الذاكرة.
  2. Pin<Smaht<T>> على أي ضمانات "خاصة" ، باستثناء أنه يُرجع Pin<&mut T> و Pin<&T> عندما يُطلب منه (وهو فقط ضمان الأمان القياسي ، نظرًا لأن واجهات برمجة التطبيقات آمنة ).
    2-أ. هل نريد ضمانًا DerefPure ، حيث يلزم Pin<Smaht<T>> لإرجاع نفس القيمة ما لم يتم تغييرها؟ هل يريد أحد ذلك؟

تصحيح حول الثابت.

ثابت Pin<Smaht<T>> هو:

  1. استدعاء Deref::deref(&self.inner) سيعطي Pin<&T::Target> صالحًا (لاحظ أن &self.inner لا يلزم أن يكون آمنًا &Smaht<T> ).
  2. إذا كان Smaht<T>: DerefMut ، استدعاء DerefMut::deref_mut(&mut self.inner) سيعطي Pin<&mut T::Target> صالحًا (لاحظ أن &mut self.inner لا يلزم أن يكون آمنًا &mut Smaht<T> ).
  3. استدعاء المدمر من self.inner لتدميره لا بأس به طالما أنه آمن للتدمير.
  4. لا يجب أن يكون self.inner آمنًا Smaht<T> - ليس من الضروري دعم وظائف أخرى.

تم نشر بعض التعليقات في منشور reddit حول اقتراح withoutboats :

  • (متعدد) Own اسم غريب ، انظر أدناه للتعرف على الطرق الممكنة لحل هذه المشكلة.

  • يسأل ryani لماذا التطبيقات التي تقيد Deref<Target = T> لها هذا العام الإضافي T ؟ ألا يمكنك استخدام P::Target ؟

  • يسأل jnicklas ما هو الغرض من السمة Own ، ماذا عن إضافة pinned الطرق المتأصلة حسب الاصطلاح؟ هذا يعني أن مستخدمي واجهة برمجة التطبيقات (API) لا يحتاجون إلى استيراد السمة ، ولكنك تفقد القدرة على أن تكون عامًا على المُنشئين القابلين للربط. هل هذه الصفقة الكبيرة؟ يبدو أن الدافع وراء هذه السمة غير مدفوع بشكل كافٍ: _ [هم] لديهم نفس الشكل بعد كل شيء.

  • يسأل RustMeUp (نفسي) ، في السمة Own ، ما هو الغرض من طريقة own ؟ لماذا لا تترك السمة فقط pinned ليتم تنفيذها بواسطة الأنواع التي تريد الاشتراك؟ هذا يسمح للسمة بأن تكون آمنة ويسمح للسمة أن تُسمى Pinned والتي تبدو أقل صعوبة من Own . يبدو أن سبب الطريقة الخاصة به دافع غير كافٍ.

سأضيف سؤالا آخر:

لماذا لا يتم تقييد Pin<P> نفسه ولا Pin<P>::new_unchecked على P: Deref ؟

#[derive(Copy, Clone)]
pub struct Pin<P> {
    pointer: P,
}

impl<P: Deref> Pin<P> { // only change
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

أو

#[derive(Copy, Clone)]
pub struct Pin<P: Deref> { // changed
    pointer: P,
}

impl<P: Deref> Pin<P> { // changed
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

كلاهما يمنع Pin<P> حيث P: !Deref مثيلات غير مجدية لأنه لا يوجد أي شيء يمكنك استخدامه ما لم تتم إضافة طرق إضافية أعتقد...

فيما يلي ملخص خاص بي وتعليقات ryani معالجتها.

رياني يسأل لماذا التطبيقات التي تقيد Derefلديك هذا العام الإضافي T؟ ألا يمكنك فقط استخدام P :: Target؟

لا يوجد فرق على الإطلاق بين هذين الضمنيين بخلاف كيفية كتابتهما.

لماذا لا يوجد Pin

نفسها ولا دبوس

:: new_unchecked مقصور على P: Deref؟

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


التخطيط لتنفيذ هذا بعد وصول # 53227 ، ولكن لن يتم تنفيذ السمة Own لأنها مثيرة للجدل. في الوقت الحالي ، مجرد مُنشئ متأصل pinned على Box ، Rc و Arc .

متابعة إلى حد ما لما كتبه @ arielb1 ، أرى أن Pin هنا يعمل بالفعل على "مُنشئ نوع المؤشر" - أشياء مثل Box أو &'a mut التي لها "نوع" * -> * . بالطبع ، ليس لدينا بالفعل بناء جملة لذلك ، ولكن هذه طريقة يمكنني من خلالها فهم هذا من حيث الثوابت. لا يزال لدينا 4 (ثوابت لكل نوع كما في منشور مدونتي : مملوكة ، مشتركة ، مملوكة مثبتة ، مثبتة - مشتركة.

أعتقد أن الشكل المناسب يتطلب هذا العرض ، لأن الفكرة هي أن Pin<Ptr><T> يأخذ T ثوابت ، يحولها (باستخدام الثوابت المثبتة في كل مكان) ، ثم يطبق Ptr منشئ Owned ، لكن هذا شيء خططت له على المدى الطويل على أي حال.) من حيث الشكلية ، يفضل المرء حقًا كتابة Ptr<Pin<T>> ، لكننا حاولنا هذا ولا يعمل بشكل جيد مع الصدأ.

الوعد ، إذن ، الذي يقدمه مُنشئ نوع المؤشر عند "الاشتراك" في Pin هو أن تطبيق Deref / DerefMut سيكون آمنًا: ذلك عندما يكون الإدخال في الواقع Ptr تطبيقه على متغير Pinned-Owned / Shared من النوع ، سيلبي الناتج المتغير المملوك من قبل Pinnd / Shared من هذا النوع.

هاتان هما في الحقيقة "الطريقتان غير الآمنتان" الوحيدتان هنا (بمعنى أنهما يجب أن تكونا حذرين بشأن الثوابت):

impl<P, T> Pin<P> where
    P: Deref<Target = T>,
{
    pub fn as_ref(this: &Pin<P>) -> Pin<&T> {
        Pin { pointer: &*this.pointer }
    }
}

impl<P, T> Pin<P> where
    P: DerefMut<Target = T>,
{
    pub fn as_mut(this: &mut Pin<P>) -> Pin<&mut T> {
        Pin { pointer: &mut *this.pointer }
}

يمكن تنفيذ أي شيء آخر آمن للاستخدام بالإضافة إلى ذلك باستخدام رمز آمن ، مع استغلال أن Pin<&T> -> &T هو تحويل آمن ، وبالنسبة لـ Unpin تنفيذ نفس الحجوزات مقابل Pin<&mut T> -> &mut T .

أفضل ما إذا كان بإمكاننا تغيير عمليات التنفيذ Deref و DerefMut مقابل Pin إلى شيء مثل

impl<'a, T: Unpin> Pin<&'a mut T> {
    pub fn unpin_mut(this: Pin<&'a mut T>) -> &'a mut T {
        this.pointer
    }
}

impl<'a, T> Pin<&'a T> {
    // You cannot move out of a shared reference, so "unpinning" this
    // is always safe.
    pub fn unpin_shr(this: Pin<&'a T>) -> &'a T {
        this.pointer
    }
}

// The rest is now safe code that could be written by users as well
impl<P, T> Deref for Pin<P> where
    P: Deref<Target = T>,
{
    type Target = T;
    fn deref(&self) -> &T {
        Pin::unpin_shr(Pin::as_ref(self))
    }
}

impl<P, T> DerefMut for Pin<P> where
    P: DerefMut<Target = T>,
    T: Unpin,
{
    fn deref_mut(&mut self) -> &mut T {
        Pin::unpin_mut(Pin::as_mut(self))
    }
}

سيؤدي ذلك إلى أن هذين الشخصين لا يفعلان شيئًا جديدًا ، فهما يؤلفان فقط as_ref / as_mut مع تحويل آمن من Pin<&[mut] T> إلى &[mut] T - - مما يجعل من الواضح أن as_ref / as_mut هو الشيء الوحيد الذي يجب على منشئي نوع المؤشر الآخرين القلق بشأنه.


يحتوي PinMut على طريقة borrow مع &mut self لتسهيل إعادة الاقتراض (نظرًا لأن هذا يتطلب self ، نحصل على الاقتراض التلقائي). تعمل هذه الطريقة تمامًا مثل Pin::as_mut . أفترض أننا نريد شيئًا كهذا هنا أيضًا؟

لقد لاحظت للتو أن الشرائح بها get_unchecked_mut ، لكن الدبوس يحتوي على get_mut_unchecked . يبدو أننا يجب أن نتمسك بشيء ثابت؟

هل يمكن لشخص إضافة هذا إلى مخاوف rfcbot؟

rfcbot قلق get_mut_unchecked_mut_mut

لقد أدركت للتو أننا نسينا موقفًا واحدًا حيث سيقوم rustc بنسخ الأشياء حوله - ربما لا يكون الأمر كبيرًا ، في الوقت الحالي. أنا أتحدث عن الهياكل المعبأة. إذا كان الهيكل المعبأ يحتوي على حقل يحتاج إلى الإسقاط ، فسيقوم rustc بإرسال رمز لنسخ بيانات هذا الحقل إلى مكان محاذي ، ثم استدعاء drop على ذلك. هذا للتأكد من أن &mut تم تمريره إلى drop محاذي فعليًا.

بالنسبة لنا ، هذا يعني أن البنية repr(packed) يجب ألا تكون "هيكلية" أو "تكرارية". التثبيت - لا يمكن اعتبار حقولها مثبتة حتى لو كانت البنية نفسها. على وجه الخصوص ، من غير الآمن استخدام pin-accessor-macro في مثل هذا الهيكل. يجب أن يضاف ذلك إلى وثائقها.

يبدو هذا وكأنه بندقية قدم عملاقة يجب لصقها على جميع مستندات التثبيت.

alercah لا أعتقد أنها أكثر بكثير من مجرد القدرة الحالية على تضمين Unpin أو Drop - من المؤكد أن كليهما أكثر شيوعًا إلى جانب القيم المثبتة أكثر من #[repr(packed)] .

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

حسنًا ، هل يعني هذا أيضًا أن جميع الهياكل المعبأة يمكن أن تكون Unpin بغض النظر عن أنواع الحقول نظرًا لأن التثبيت ليس تكراريًا؟

alercah أنا لست على دراية فائقة مع كيفية تنفيذ أنواع معبأة، ولكن أعتقد آمنة لدبوس إلى نوع معبأة، ولكن ليس إلى دبوس من خلال نوع معبأة. لذا فإن الحالة الوحيدة التي لا تتحكم فيها بالنوع المعبأ هي إذا كنت تقوم بإسقاط حقل عام من نوع شخص آخر ، والذي قد يكون غير آمن تمامًا بسبب Drop أو أي شيء آخر. بشكل عام ، يبدو من غير المستحسن تثبيت المشروع في حقول شخص آخر ما لم يقدموا إسقاطًا دبوسيًا يشير إلى أنه آمن.

حسنًا ، هل يعني هذا أيضًا أن جميع الهياكل المعبأة يمكن أن تكون Unpin بغض النظر عن أنواع الحقول نظرًا لأن التثبيت ليس تكراريًا؟

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

كان علي أن أتعلم Pin API بسبب عملي مع Futures ، ولدي سؤال.

  • ينفذ Box Unpin دون قيد أو شرط.

  • ينفذ Box DerefMut دون قيد أو شرط.

  • تعمل الطريقة Pin::get_mut دائمًا على &mut Box<T> (لأن Box ينفذ Unpin دون قيد أو شرط).

مجتمعة ، تسمح بنقل قيمة مثبتة برمز آمن تمامًا .

هل هذا مقصود؟ ما هو السبب المنطقي لماذا هذا آمن القيام به؟

يبدو أن لديك Pin<&mut Box<...>> ، الذي يربط Box ، وليس الأشياء الموجودة داخل Box . من الآمن دائمًا نقل Box ، لأن Box لا يخزن أبدًا إشارات إلى موقعه على المكدس.

ما تريده على الأرجح هو Pin<Box<...>> . رابط الملعب .

تحرير: لم تعد دقيقة

Pauan لمجرد تثبيت Box لا يعني الشيء _ داخل_ أنه مثبت. أي كود يعتمد على هذا سيكون غير صحيح.

ربما يكون الشيء الذي تبحث عنه هو PinBox ، والذي لا يسمح بالسلوك الذي ذكرته ، ويسمح لك بالحصول على PinMut<Foo> .

tikue لا يزال من الممكن الخروج من Pin<Box<...>> ، وهو ما أعتقد أنه ما كانوا يحاولون تجنبه.

tmandry صححني إذا كنت مخطئًا ، لكن Pin<Box<...>> يثبت الشيء داخل Box ، وليس الصندوق نفسه. في المثال الأصلي لـ Pauan ، كان لديهم Pin<&mut Box<...>> ، والذي قام فقط بتثبيت Box . شاهد رابط الملعب الخاص بي الذي يوضح كيف يمنع Pin<Box<...>> الحصول على مرجع قابل للتغيير إلى الشيء الموجود في المربع.

لاحظ أن PinBox تمت إزالته مؤخرًا ، وأن Pin<Box<T>> له نفس الدلالات مثل PinBox .

تمت إزالة tmandry PinBox<T> واستبداله بـ Pin<Box<T>> في Nightly (رابط المستند الذي قدمته خاص بالثابت). ها هو الرابط الليلي الصحيح.

أوه ، يجب أن تكون القواعد قد تغيرت منذ أن استخدمت هذه القواعد آخر مرة. اسف لخلط الامور.

tmandry نعم ، كانت التغييرات حديثة جدًا. نظرًا لأن الأمور لا تزال في حالة تغير مستمر ، فمن الصعب مواكبة جميع التغييرات.

تعليق tikue صحيح. عليك أن تتذكر أن المسامير تثبت مستوى واحد فقط من المراوغة لأسفل.

tikuewithoutboatstmandry شكرا على إجابات! كانت مفيدة جدا.

إذن ما هي حالات الشاغلين الآن؟ ( api-refactor & get_mut_unchecked_mut_mut ) باعتباري شخصًا ينتظر بفارغ الصبر ميزة السلسلة غير المتزامنة / في انتظار ، أتساءل ما هو الإصدار rustc الذي ستستهدفه واجهات برمجة التطبيقات Pin ؟ هل هناك تقدير؟

@ crlf0710 انظر اقتراح التثبيت .

withoutboats يبدو أنه تم؟ هل نغلق؟

حسنًا ، أعتذر إذا لم يكن هذا هو المكان المناسب لنشر هذا ، لكنني كنت أفكر في مشكلة Drop + !Unpin ، وخرجت بالفكرة التالية:

  1. من الناحية المثالية ، إذا كان Drop::drop fn(self: Pin<&mut Self>) فلن تكون هناك مشكلة. لنسمي هذا Drop PinDrop . لا يمكننا استبدال Drop بـ PinDrop بسبب مشاكل التوافق.
  1. نظرًا لأن المشكلة الوحيدة مع Drop::drop(&mut self) تتعلق بحالة Drop + !Unpin ، يمكننا اشتقاق أداة افتراضية PinDrop لـ Drop + Unpin (منذ ذلك الحين Pin<&mut T> : DerefMut<Target = T> ) واجعل PinDrop هي السمة المستخدمة تلقائيًا بواسطة rustc (بفضل Pin::new_unchecked(&mut self) ، نظرًا لأن السقوط هو الحالة الوحيدة لتثبيت المكدس عندما نفكر في ذلك).

في ما يلي دليل إثبات ملكية سطحي للفكرة: https://play.rust-lang.org/؟version=nightly&mode=debug&edition=2018&gist=9aae40afe732babeafef9dab3d7525a8

على أي حال ، يجب أن يظل هذا في beta ولا يستقر بعد ، حتى لو كان هناك استثناء. إذا كان هناك وقت يمكن أن يعتمد فيه سلوك Drop على Unpin دون كسر مواطن ، فهذا الوقت هو الآن.

danielhenrymantilla لا أرى كيف يحل ذلك مشكلة التوافق مع أجهزة الإسقاط العامة الحالية ، مثل impl<T> Drop for Vec<T> .

أنت محق في أنها تتطلب شيئًا آخر:
ربط ضمني T: Unpin على جميع الأدوية ، مع إلغاء الاشتراك باستخدام ?Unpin بنفس طريقة Sized

يجب أن يجعلها تصبح

impl<T> Drop for Vec<T>
where
  T : Unpin, // implicit, which implies that Vec<T> : Unpin which "upgrades" `Drop` into `PinDrop`

ربط ضمني T: Unpin على جميع الأدوية ، مع إلغاء الاشتراك باستخدام ?Unpin بنفس طريقة Sized

هذا له تأثير كبير على التصميم العام لواجهة برمجة التطبيقات ، وقد تمت مناقشته على نطاق واسع كجزء من مقترحات ?Move . على سبيل المثال ، قد يعني ذلك أن العديد من المكتبات الحالية ستحتاج إلى التحديث للعمل مع التثبيت. كان الاستنتاج هو أن استخدام حل المكتبة فقط مثل ما حصلنا عليه الآن أفضل لأنه لا يتطلب أيًا من هذا.

نعم ، التكلفة الباهظة على المدى القصير نظرًا لأن جميع المكتبات الحالية ستحتاج إلى التحديث لتكون متوافقة مع !Unpin ، ولكن على المدى الطويل سننتهي بـ Drop "أكثر أمانًا". لم يبد الأمر بهذا السوء في البداية ، لأننا على الأقل لا نكسر أي شيء.

لكن هذا مصدر قلق عادل (لم أكن أعلم أنه تم طرحه سابقًا ؛ شكرًا لك على الإشارة إلى ذلك ، RalfJung ) ، وأعتقد أن العيوب العملية على المدى القصير drop(Pin<&mut Self>) .

هل كانت هناك أي مناقشة حول تنفيذ Hash لأنواع Pin على العناوين؟

Pin المحتمل أن يكون لدى Hash ببساطة بتفويض المؤشر المتضمن ، ولا توجد أسبقية للتجزئة بناءً على العناوين (ولا أرى أي سبب يدعو إلى تغيير كيفية تثبيت قيمة يتم تجزئته).

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات