Rust: النقابات بدون علامات (تتبع المشكلة لـ RFC 1444)

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

مشكلة تتبع rust-lang / rfcs # 1444.

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

  • [x] هل التعيين مباشرة إلى حقل الاتحاد يؤدي إلى انخفاض المحتويات السابقة؟
  • [x] عند الخروج من أحد مجالات الاتحاد ، هل تعتبر المجالات الأخرى باطلة؟ ( 1 ، 2 ، 3 ، 4 )
  • [] في أي ظروف يمكنك تنفيذ Copy للنقابة؟ على سبيل المثال ، ماذا لو كانت بعض المتغيرات من نوع غير نسخ؟ كل المتغيرات؟
  • [] ما هو التفاعل الموجود بين النقابات وتحسينات تخطيط التعداد؟ (https://github.com/rust-lang/rust/issues/36394)

الإصدارات المفتوحة ذات الاستيراد العالي:

  • [x] https://github.com/rust-lang/rust/issues/47412 - يقبل مدقق عدم الأمان المستند إلى MIR أحيانًا الوصول غير الآمن إلى حقول الاتحاد في وجود حقول غير مأهولة
B-RFC-approved B-unstable C-tracking-issue F-untagged_unions T-lang disposition-merge finished-final-comment-period

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

nrc
حسنًا ، المجموعة الفرعية واضحة إلى حد ما - "اتحادات FFI" ، أو "اتحادات C" ، أو "اتحادات ما قبل C ++ 11" - حتى لو لم تكن نحوية. كان هدفي الأولي هو تثبيت هذه المجموعة الفرعية في أسرع وقت ممكن (هذه الدورة ، بشكل مثالي) بحيث يمكن استخدامها في مكتبات مثل winapi .
لا يوجد شيء مشكوك فيه بشكل خاص حول المجموعة الفرعية المتبقية وتنفيذها ، فهي ليست ملحة وتحتاج إلى الانتظار لفترة غير واضحة من الوقت حتى تكتمل عملية RFC "Unions 1.2". ستكون توقعاتي هي تثبيت الأجزاء المتبقية في 1 أو 2 أو 3 دورات بعد تثبيت المجموعة الفرعية الأولية.

ال 210 كومينتر

ربما فاتني ذلك في المناقشة حول RFC ، لكن هل أنا محق في التفكير في أن مدمري متغيرات الاتحاد لا يتم تشغيلهم أبدًا؟ هل سيتم تشغيل أداة التدمير لـ Box::new(1) في هذا المثال؟

union Foo {
    f: i32,
    g: Box<i32>,
}

let mut f = Foo { g: Box::new(1) };
f.g = Box::new(2);

sfackler ما أفهمه حاليًا هو أن f.g = Box::new(2) _ سوف يقوم بتشغيل أداة التدمير ولكن f = Foo { g: Box::new(2) } لن يتم تشغيله. أي أن التعيين إلى قيمة Box<i32> lvalue سيؤدي إلى انخفاض كما هو الحال دائمًا ، ولكن التعيين إلى قيمة Foo lvalue لن يحدث.

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

sfackler لأنواع Drop ، نعم ، هذا ما أفهمه. إذا لم تكن صالحة من قبل ، فأنت بحاجة إلى استخدام نموذج المُنشئ Foo أو ptr::write . من grep السريع ، لا يبدو أن RFC صريح بشأن هذه التفاصيل ، رغم ذلك. أراه بمثابة مثيل للقاعدة العامة التي تقول إن الكتابة إلى Drop lvalue تؤدي إلى استدعاء المدمر.

هل يجب أن يكون الاتحاد مع متغيرات Drop بمثابة نسالة؟

في يوم الجمعة ، 8 أبريل 2016 ، كتب سكوت أولسون [email protected] :

sfackler https://github.com/sfackler لأنواع Drop ، نعم ، هذا هو
فهم. إذا لم تكن صالحة من قبل ، فأنت بحاجة إلى استخدام Foo
شكل المنشئ أو ptr :: write. من grep سريع ، لا يبدو
RFC صريح حول هذه التفاصيل ، رغم ذلك.

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرةً أو قم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment -207634431

في 8 أبريل 2016 ، الساعة 3:36:22 مساءً بتوقيت المحيط الهادئ ، كتب سكوت أولسون [email protected] :

sfackler لأنواع Drop ، نعم ، هذا ما أفهمه. اذا هم
لم تكن صالحة من قبل ، فأنت بحاجة إلى استخدام نموذج المُنشئ Foo أو
ptr::write . من grep سريع ، لا يبدو أن RFC كذلك
صريحة حول هذه التفاصيل ، على الرغم من.

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

نعم ، هذا النهج يبدو أقل خطورة بالنسبة لي أيضًا.

سيؤدي عدم السقوط عند التعيين إلى حقل توحيد إلى جعل f.g = Box::new(2) يتصرف بشكل مختلف عن let p = &mut f.g; *p = Box::new(2) ، لأنه لا يمكنك جعل الحالة الأخيرة _ لا _ تسقط. أعتقد أن مقاربتي أقل إثارة للدهشة.

إنها ليست مشكلة جديدة أيضًا ؛ unsafe المبرمجين foo = bar هو UB إذا كان foo غير مهيأ و Drop .

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

كما أنني لا أنوي استخدام أنواع Drop في النقابات ، لذا فإن أيًا من الطريقتين لا يهمني طالما أنهما ثابتان.

لا أنوي استخدام إشارات قابلة للتغيير للنقابات ، وربما
فقط "ذات العلامات الغريبة" مع Into

في يوم الجمعة ، 8 أبريل 2016 ، كتب Peter Atashian [email protected] :

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

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرةً أو قم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment -207653168

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

nikomatsakis بقدر ما أجد أنه من المحرج tsion المذكورة تبدو شبه حتمية. أعتقد أن هذا قد يكون مجرد مسدس مرتبط بالكود الذي يعطل النسالة عن قصد لوضع نوع مع Drop في اتحاد. (وينبغي أن يكون الشرح المختصر لها في النص التوضيحي لذلك النص.)

وأود أن أكرر أن المبرمجين unsafe يجب أن يعرفوا بالفعل بشكل عام أن a = b يعني drop_in_place(&mut a); ptr::write(&mut a, b) لكتابة رمز آمن. إن عدم إسقاط حقول الاتحاد سيكون استثناءً أكثر للتعلم ، وليس أقل من ذلك.

(ملاحظة: لا يحدث الانخفاض عندما يكون a معروفًا _ بشكل ثابت أنه غير مهيأ بالفعل ، مثل let a; a = b; .)

لكنني أؤيد وجود تحذير افتراضي ضد المتغيرات Drop في النقابات التي يتعين على الناس #[allow(..)] لأن هذه تفاصيل غير واضحة إلى حد ما.

tsion ، هذا ليس صحيحًا بالنسبة إلى a = b وربما يكون صحيحًا في بعض الأحيان فقط لـ a.x = b لكن هذا صحيح بالتأكيد لـ *a = b . هذا الشك هو ما جعلني مترددًا حيال ذلك. على سبيل المثال ، هذا يجمع:

fn main() {
  let mut x: (i32, i32);
  x.0 = 2;
  x.1 = 3;
}

(على الرغم من فشل محاولة طباعة x لاحقًا ، لكنني أعتبر هذا خطأ)

nikomatsakis هذا المثال جديد بالنسبة لي. أعتقد أنني كنت سأعتبره خطأً يجمعه هذا المثال ، نظرًا لتجربتي السابقة.

لكنني لست متأكدًا من أنني أرى أهمية هذا المثال. لماذا ما قلته غير صحيح بالنسبة لـ a = b وأحيانًا فقط a.x = b ؟

لنفترض ، إذا كان x.0 له نوع به أداة تدمير ، فمن المؤكد أن هذا المدمر يسمى:

fn main() {
    let mut x: (Box<i32>, i32);
    x.0 = Box::new(2); // x.0 statically know to be uninit, destructor not called
    x.0 = Box::new(3); // x.0 destructor is called before writing new value
}

ربما فقط ضد هذا النوع من الكتابة؟

وجهة نظري هي أن = لا _دائماً_ يدير المدمر ؛ عليه
يستخدم بعض المعرفة حول ما إذا كان الهدف معروفًا أم لا
مهيأ.

في الثلاثاء ، 12 أبريل 2016 الساعة 04:10:39 مساءً -0700 ، كتب سكوت أولسون:

nikomatsakis هذا المثال جديد بالنسبة لي. أعتقد أنني كنت سأعتبره خطأً يجمعه هذا المثال ، نظرًا لتجربتي السابقة.

لكنني لست متأكدًا من أنني أرى أهمية هذا المثال. لماذا ما قلته غير صحيح بالنسبة لـ a = b وأحيانًا فقط بالنسبة لـ 'ax = b'؟

لنفترض ، إذا كان x.0 له نوع به أداة تدمير ، فمن المؤكد أن هذا المدمر يسمى:

fn main() {
    let mut x: (Box<i32>, i32);
    x.0 = Box::new(2); // x.0 statically know to be uninit, destructor not called
    x.0 = Box::new(3); // x.0 destructor is called
}

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

يتم تشغيل أداة التدمير إذا تم تعيين علامة الإسقاط.

لكنني أعتقد أن هذا النوع من الكتابة مربك على أي حال ، فلماذا لا تمنعه ​​فقط؟ يمكنك دائمًا القيام بـ *(&mut u.var) = val .

وجهة نظري هي أن = لا _دائماً_ يدير المدمر ؛ يستخدم بعض المعرفة حول ما إذا كان الهدف معروفًا بالتهيئة أم لا.

nikomatsakis لقد ذكرت بالفعل ما يلي:

(ملاحظة: لا يحدث الانخفاض عندما يكون a معروفًا بشكل ثابت أنه غير مهيأ بالفعل ، مثل let a؛ a = b ؛.)

لكنني لم أحسب الفحص الديناميكي لأعلام الإسقاط ، لذلك هذا بالتأكيد أكثر تعقيدًا مما كنت أعتقده.

tsion

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

هل يجب السماح بأنواع Drop في النقابات؟ إذا كنت أفهم الأشياء بشكل صحيح ، فإن السبب الرئيسي لوجود نقابات في Rust هو التفاعل مع كود C الذي يحتوي على نقابات ، ولا يحتوي C حتى على مدمرات. لجميع الأغراض الأخرى ، يبدو أنه من الأفضل استخدام رمز Rust enum .

هناك حالة استخدام صالحة لاستخدام الاتحاد لتنفيذ نوع NoDrop والذي يمنع السقوط.

وكذلك استدعاء هذا الكود يدويًا عبر drop_in_place أو ما شابه.

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

هل سيكون من الممكن منع واضعي الحقول ولكن تتطلب استبدال نقابة كاملة؟ في هذه الحالة ، إذا قام الاتحاد بتنفيذ إسقاط الاتحاد بالكامل ، فسيتم استدعاء القيمة المستبدلة كما هو متوقع.

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

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

union SlotInner<V> {
    next_empty: usize, /* index of next empty slot */
    value: V,
}

struct Slot<V> {
    inner: SlotInner<V>,
    version: u64 /* even version -> is_empty */
}

nikomatsakis أود أن أقترح إجابة محددة للسؤال المدرج حاليًا على أنه لم يتم حله هنا.

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

هل من المنطقي تقديم طلب سحب RFC لتعديل RFC1444 لتوثيق هذا السلوك؟

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

aturon شكرا. لقد قدمت RFC PR الجديد https://github.com/rust-lang/rfcs/issues/1663 مع هذه التوضيحات إلى RFC1444 لحل هذه المشكلة.

( aturon ، يمكنك التحقق من هذا السؤال الذي لم يتم حله الآن.)

لدي بعض التنفيذ الأولي في https://github.com/petrochenkov/rust/tree/union.

الحالة: تم التنفيذ (أخطاء modulo) ، تم إرسال العلاقات العامة (https://github.com/rust-lang/rust/pull/36016).

تضمين التغريدة تبدو رائعة حتى الآن.

لست متأكدًا تمامًا من كيفية التعامل مع النقابات ذات الحقول غير Copy في مدقق الحركة.
لنفترض أن u قيمة تمت تهيئتها لـ union U { a: A, b: B } ونحن الآن نخرج من أحد الحقول:

1) A: !Copy, B: !Copy, move_out_of(u.a)
هذا بسيط ، يتم أيضًا وضع u.b في حالة غير مهيأة.
التحقق من الصحة: ​​يجب أن يتصرف union U { a: T, b: T } تمامًا مثل الاسم المستعار للحقل struct S { a: T } + تمامًا.

2) A: Copy, B: !Copy, move_out_of(u.a)
من المفترض أن u.b يجب أن تتم تهيئته ، لأن move_out_of(u.a) هو ببساطة memcpy ولا يغير u.b بأي شكل من الأشكال.

2) A: !Copy, B: Copy, move_out_of(u.a)
هذه أغرب حالة. من المفترض أيضًا أن يتم وضع u.b في حالة غير مهيأة على الرغم من كونها Copy . يمكن أن تكون قيم Copy غير مهيأة (على سبيل المثال let a: u8; ) ، ولكن تغيير حالتها من التهيئة إلى غير المهيأة شيء جديد ، AFAIK.

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

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

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

بموجب هذه القواعد ، أتوقع أن تنفذ النقابات Copy إذا تم تنفيذ النسخ لهم. على عكس البنيات / التعدادات ، لن تكون هناك فحوصات داخلية: يمكنك دائمًا تنفيذ نسخة لنوع اتحاد إذا أردت.

اسمحوا لي أن أقدم بعض الأمثلة للتوضيح:

union Foo { ... } // contents don't matter

هذا الاتحاد هو أفيني ، لأنه لم يتم تنفيذ Copy .

union Bar { x: Rc<String> }
impl Copy for Bar { }
impl Clone for Bar { fn clone(&self) -> Self { *self } }

نوع الاتحاد هذا Bar هو نسخة ، لأنه تم تنفيذ Copy .

لاحظ أنه إذا كان Bar عبارة عن هيكل ، فسيكون من الخطأ تنفيذ Copy بسبب نوع الحقل x .

هاه ، أعتقد أنني لا أجيب على سؤالك بالفعل ، بعد أن أعدت قراءته. =)

حسنًا ، أدركت أنني لم أجيب على سؤالك على الإطلاق. لذا دعني أحاول مرة أخرى. باتباع مبدأ "bit bucket" ، أتوقع _ ما زلت_ أنه يمكننا الخروج من الاتحاد حسب الرغبة. ولكن بالطبع هناك خيار آخر يتمثل في معاملته كما نتعامل مع *mut T ، ونطلب منك استخدام ptr::read للخروج.

تحرير: لست متأكدًا تمامًا من سبب حظر مثل هذه التحركات. ربما كان لا بد من القيام بذلك أثناء التحرك - أو ربما لمجرد أنه من السهل ارتكاب خطأ ويبدو أنه من الأفضل جعل "التحركات للخارج" أكثر وضوحًا؟ أجد صعوبة في تذكر التاريخ هنا.

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

غريزتي هي أن النقابات هي في الأساس "دلو صغير".

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

التفسير هو أن الاتحاد هو تعداد لا نعرف تمييزه ، أي يمكننا ضمان أن يكون لواحد على الأقل من متغيرات الاتحاد قيمة صالحة (ما لم يتم تضمين رمز غير آمن).

تدعم جميع قواعد الاستعارة / النقل في التنفيذ الحالي هذا الضمان ، وفي نفس الوقت هذا هو التفسير الأكثر تحفظًا ، والذي يسمح لنا بالسير إما بالطريقة "الآمنة" (على سبيل المثال ، السماح بالوصول الآمن إلى النقابات ذات الحقول المكتوبة بشكل متساوٍ ، وهذا يمكن أن يكون مفيدة ) أو طريقة "bit bucket" في المستقبل ، عندما يتم جمع المزيد من الخبرة مع نقابات الصدأ.

في الواقع ، أود أن أجعلها أكثر تحفظًا كما هو موضح في https://github.com/rust-lang/rust/pull/36016#issuecomment -242810887

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

التفسير هو أن الاتحاد هو تعداد لا نعرف تمييزه ، أي يمكننا ضمان أن يكون لواحد على الأقل من متغيرات الاتحاد قيمة صالحة (ما لم يتم تضمين رمز غير آمن).

لاحظ أن التعليمات البرمجية غير الآمنة يتم تضمينها دائمًا ، عند العمل مع نقابة ، نظرًا لأن كل وصول إلى حقل غير آمن.

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

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

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

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

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

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

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

يجب أن نتأكد من أن لدينا قائمة كاملة بهذه المناطق الرمادية

سأقوم بتعديل اتحاد RFC في المستقبل غير البعيد! تفسير "التعداد" له نتائج ممتعة جدًا.

يعد النقل في وقت التحويل أمرًا سيئًا بشكل عام (ما لم نحاول تحويل المترجم إلى نوع من محاكي الهدف الجزئي)

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

و eddyb يضغط لاستبدال التقييم المستمر لـ rustc بنسخة من Miri.

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

إحدى الحجج ضد تفسير "أكثر من متغير" هي كيف تتصرف النقابات في التعبيرات الثابتة ...

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

تفسيري هو أنه في وقت التشغيل لا تزال المتغيرات غير النشطة غير نشطة ولكن يمكن الوصول إليها إذا كان التخطيط متوافقًا مع المتغير النشط للاتحاد

حسنًا ، أحاول أن أفكر كيف يختلف هذا عن القول بأن الاتحاد ينتمي إلى كل تلك المتغيرات في وقت واحد. لا أرى فرقًا بعد. :)

أشعر أن هذا التفسير له تفاعلات غريبة مع الحركات بشكل عام. على سبيل المثال ، إذا كانت البيانات علامة X "حقًا" ، وتفسرها على أنها حرف Y ، ولكن Y عبارة عن علاقة أفقية ، فهل تظل علامة X؟

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

سأقوم بتعديل اتحاد RFC في المستقبل غير البعيد! تفسير "التعداد" له نتائج ممتعة جدًا.

هذه الثقة! ستحاول ؛)

احرص على إلقاء بعض التفاصيل الإضافية حول التغييرات الملموسة التي تفكر فيها؟

احرص على إلقاء بعض التفاصيل الإضافية حول التغييرات الملموسة التي تفكر فيها؟

وصف أكثر تفصيلاً للتنفيذ (أي توثيق أفضل) ، وبعض الامتدادات الصغيرة (مثل النقابات الفارغة و .. في أنماط النقابات) ، بديلين رئيسيين (متناقضين) لتطور النقابات - مساحة غير آمنة وأقل تقييدًا تفسير أكثر أمانًا وأكثر تقييدًا "تعداد مع تمييز غير معروف" - وعواقبها على مدقق النقل / التهيئة ، و Copy impls ، unsafe ty من الوصول إلى الحقل ، إلخ.

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

union U { a: u8, b: () }
let u = U { b: () };
let a = u.a; // most probably an UB, equivalent to reading from `mem::uninitialized()`

لكن هذه منطقة صعبة للغاية.

يبدو من المرجح أن الدلالات عبر الحقول هي في الأساس مؤشر يلقي ، أليس كذلك؟
_ (_ () كـ * u8)

في الخميس 1 سبتمبر، 2016، فاديم Petrochenkov [email protected]
كتب:

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

الاتحاد U {a: u8، b: ()}
دع u = U {b: ()} ؛
دع أ = ua ؛ // على الأرجح UB ، أي ما يعادل القراءة من mem::uninitialized()

لكن هذه منطقة صعبة للغاية.

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment -244154751 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABxXhi68qRITTFW5iJn6omZQQBQgzweNks5qlw4qgaJpZM4IDXsj
.

أليس الوصول الميداني غير آمن دائمًا؟

في الخميس 1 سبتمبر، 2016، فاديم Petrochenkov [email protected]
كتب:

احرص على إلقاء بعض التفاصيل الإضافية حول التغييرات الملموسة التي تفكر فيها؟

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

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment -244151164 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABxXhuHStN8AFhR3KYDU27U29MiMpN5Bks5qlws9gaJpZM4IDXsj
.

أليس الوصول الميداني غير آمن دائمًا؟

يمكن جعلها آمنة في بعض الأحيان ، على سبيل المثال

  • التخصيص لحقول الاتحاد غير القابلة للتدمير أمر آمن.
  • أي وصول إلى الحقول من union U { f1: T, f2: T, ..., fN: T } (أي أن جميع الحقول لها نفس النوع) آمن في تفسير "التعداد مع تمييز غير معروف".

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

يتم حاليًا اختبار دعم النقابات في أحدث طراز من git. كل ما جربته يعمل بشكل مثالي.

واجهت حالة مثيرة للاهتمام في مدقق الحقل الميت. جرب الكود التالي:

#![feature(untagged_unions)]

union U {
    i: i32,
    f: f32,
}

fn main() {
    println!("{}", std::mem::size_of::<U>());
    let u = U { f: 1.0 };
    println!("{:#x}", unsafe { u.i });
}

ستحصل على هذا الخطأ:

warning: struct field is never used: `f`, #[warn(dead_code)] on by default

يبدو أن مدقق dead_code لم يلاحظ التهيئة.

(لقد قدمت بالفعل PR # 36252 حول استخدام "حقل البنية" ، وتغييره إلى "حقل" فقط.)

لا يمكن للنقابات أن تحتوي حاليًا على حقول ذات حجم ديناميكي ، ولكن RFC لا تحدد هذا السلوك في أي من الحالتين:

#![feature(untagged_unions)]

union Foo<T: ?Sized> {
  value: T,
}

انتاج:

error[E0277]: the trait bound `T: std::marker::Sized` is not satisfied
 --> <anon>:4:5
  |
4 |     value: T,
  |     ^^^^^^^^ trait `T: std::marker::Sized` not satisfied
  |
  = help: consider adding a `where T: std::marker::Sized` bound
  = note: only the last field of a struct or enum variant may have a dynamically sized type

لا تعمل الكلمة الأساسية السياقية خارج سياق جذر الوحدة النمطية / الصندوق:

fn main() {
    // all work
    struct Peach {}
    enum Pineapple {}
    trait Mango {}
    impl Mango for () {}
    type Strawberry = ();
    fn woah() {}
    mod even_modules {
        union WithUnions {}
    }
    use std;

    // does not work
    union Banana {}
}

يبدو وكأنه ثؤلول الاتساق سيئة جدا.

تضمين التغريدة
هل تستخدم بعض الإصدارات القديمة من rustc عن طريق الصدفة؟
لقد راجعت للتو مثالك على playpen وهو يعمل (أخطاء modulo "blank union").
هناك أيضًا اختبار اجتياز التحقق من هذا الموقف المحدد - https://github.com/rust-lang/rust/blob/master/src/test/run-pass/union/union-backcomp.rs.

petrochenkov آه ، لقد استخدمت play.rlo ، لكن يبدو أنه ربما عاد إلى stable أو شيء من هذا القبيل. لا تهتم بي إذن.

أعتقد أن النقابات ستحتاج في النهاية إلى دعم الحقول الآمنة ، التوأم الشرير للحقول غير الآمنة من هذا الاقتراح .
نسخة إلى https://github.com/rust-lang/rfcs/issues/381#issuecomment -246703410

أعتقد أنه سيكون من المنطقي وجود طريقة لإعلان "النقابات الآمنة" ، بناءً على معايير مختلفة.

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

joshtriplett ستحتاج أيضًا إلى التأكد من عدم وجود "ثغرات" في الأنواع التي يمكنك من خلالها قراءة البيانات غير المهيأة. كما أود أن أقول: "البيانات غير المهيأة هي إما بيانات عشوائية غير متوقعة أو مفتاح SSH الخاص بك ، أيهما أسوأ."

أليس &T نسخ ولا يُسقط؟ ضع ذلك في اتحاد "آمن" مع usize ، وستحصل على مولد مراجع محتال. لذلك يجب أن تكون القواعد أكثر صرامة من ذلك بقليل.

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

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

  • u8 و bool لهما نفس الحجم ، لكن معظم قيم u8 غير صالحة لـ bool وتجاهل هذا المشغلات UB
  • &T و &U لهما نفس الحجم وهما Copy + !Drop لكل T و U (طالما أن كلاهما Sized أو لا
  • تحويل ما بين uN / iN و fN ممكن حاليًا فقط في الكود غير الآمن. أعتقد أن مثل هذه المحولات آمنة دائمًا ولكن هذا يوسع اللغة الآمنة لذا قد يكون مثيرًا للجدل.
  • يعد انتهاك الخصوصية (على سبيل المثال ، المعاقبة بين struct Foo(Bar); و Bar ) أمرًا لا بأس به ، حيث يمكن استخدام الخصوصية لدعم الثوابت ذات الصلة بالسلامة.

Amanieu عندما كنت أكتب ذلك ، كنت أقصد تضمين ملاحظة حول عدم وجود أي حشوة داخلية ، وبطريقة ما نسيت القيام بذلك. شكرا للقبض على ذلك.

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

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

u8 و bool لهما نفس الحجم ، لكن معظم قيم u8 غير صالحة للعامل المنطقي وتجاهل هذا المشغلات UB

نقطة جيدة؛ نفس المشكلة تنطبق على التعدادات.

& T و & U لهما نفس الحجم وهما Copy +! Drop لجميع T و U (طالما كلاهما بالحجم أو كلاهما)

لقد نسيت ذلك.

النقل بين uN / iN و fN ممكن حاليًا فقط في رمز غير آمن. أعتقد أن مثل هذه المحولات آمنة دائمًا ولكن هذا يوسع اللغة الآمنة لذا قد يكون مثيرًا للجدل.

متفق على النقطتين ؛ يبدو أن هذا مقبول للسماح به.

يعد انتهاك الخصوصية (على سبيل المثال المعاقبة بين البنية Foo (Bar) ؛ و Bar) أمرًا لا يستحق الذكر ، حيث يمكن استخدام الخصوصية لدعم الثوابت ذات الصلة بالسلامة.

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

النقل بين uN / iN و fN ممكن حاليًا فقط في رمز غير آمن. أعتقد أن مثل هذه المحولات آمنة دائمًا ولكن هذا يوسع اللغة الآمنة لذا قد يكون مثيرًا للجدل.

تحتوي أرقام النقطة العائمة على إشارة NaN وهو تمثيل مصيدة ينتج عنه UB.

@ retep998 هل يعمل Rust على أي منصات لا تدعم تعطيل مصائد

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

التفسير هو أن الاتحاد هو تعداد لا نعرف تمييزه ، أي يمكننا ضمان أن يكون لواحد على الأقل من متغيرات الاتحاد قيمة صالحة في أي وقت

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

سؤال حول #[repr(C)] : كما أوضحت لي #[repr(C)] ، فمن غير القانوني التخزين مع الحقل x وقراءته بالحقل y . من المفترض أن هذا يرجع إلى أننا لسنا مطالبين ببدء جميع الحقول في نفس الإزاحة.

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

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

أفكار؟

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

التفسير هو أن الاتحاد هو تعداد لا نعرف تمييزه ، أي يمكننا ضمان أن يكون لواحد على الأقل من متغيرات الاتحاد قيمة صالحة في أي وقت

أسوأ جزء هو الأجزاء المتغيرة / الميدانية ، والتي يمكن الوصول إليها مباشرة للنقابات.
ضع في اعتبارك هذا الرمز:

union U {
    a: (u8, bool),
    b: (bool, u8),
}
fn main() {
    unsafe {
        let mut u = U { a: (2, false) };
        u.b.1 = 2; // turns union's memory into (2, 2)
    }
}

جميع الحقول Copy ، لا توجد ملكية متضمنة ومدقق النقل سعيد ، لكن التعيين الجزئي للحقل غير النشط b يحول الاتحاد إلى حالة مع المتغيرات الصالحة 0 . لم أفكر في كيفية التعامل معها حتى الآن. جعل مثل هذه التعيينات UB؟ تغيير التفسير؟ شيء آخر؟

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

جعل مثل هذه التعيينات UB؟

هذا سيكون افتراضي ، نعم. عند تعيينك لـ a ، لم يكن المتغير b في مجموعة المتغيرات الصالحة ، وبالتالي استخدام u.b.1 (سواء للقراءة أو التعيين) غير صالح.

سؤال حول # [repr (C)]: كما أوضحت لي

أعتقد أن الصياغة المناسبة هنا هي أن 1) القراءة من الحقول غير "المتوافقة مع التنسيق" (هذا غامض) مع الحقول المكتوبة مسبقًا / أجزاء الحقول المكتوبة مسبقًا هي UB 2) بالنسبة إلى الاتحادات #[repr(C)] يعرف المستخدمون ما هي التخطيطات (من مستندات ABI) حتى يتمكنوا من التمييز بين UB وغير UB 3) بالنسبة إلى #[repr(Rust)] تخطيطات الاتحاد غير محددة حتى لا يتمكن المستخدمون من تحديد ما هو UB وما هو غير ذلك ، لكن WE (rustc / libstd + الخاصة بهم الاختبارات) لديها هذه المعرفة المقدسة ، حتى نتمكن من فصل القمح عن القشر واستخدام #[repr(Rust)] غير تابعة لـ UB.

4) بعد تحديد أسئلة الحجم / الخطوة وإعادة ترتيب الحقول عندما أتوقع أن يتم وضع تخطيطات الهيكل والنقابة في حجر ومحددة ، حتى يعرف المستخدمون التخطيطات أيضًا وسيكونون قادرين على استخدام اتحادات #[repr(Rust)] بحرية مثل #[repr(C)] وستختفي المشكلة.

nikomatsakis في مناقشة اتحاد RFC ، ذكر الناس الرغبة في الحصول على كود Rust الأصلي الذي يستخدم النقابات لبناء هياكل بيانات مضغوطة.

هل هناك ما يمنع الأشخاص الذين يستخدمون #[repr(C)] ؟ إذا لم يكن الأمر كذلك ، فأنا لا أرى ضرورة لتقديم أي نوع من الضمانات مقابل #[repr(Rust)] ، فقط اترك الأمر على أنه "هنا تنانين". ربما يكون من الأفضل أن يكون لديك نسالة يتم تحذيرها بشكل افتراضي للنقابات التي ليست #[repr(C)] .

@ retep998 يبدو لي أنه من المعقول أن repr(Rust) لا يضمن أي تخطيط أو تداخل معين. أود فقط أن أقترح أن repr(Rust) يجب ألا يكسر في الممارسة افتراضات الناس حول استخدام الذاكرة في الاتحاد ("ليس أكبر من العضو الأكبر").

هل يعمل Rust على أي منصات لا تدعم تعطيل مصائد النقطة العائمة؟

هذا ليس سؤالًا صالحًا لطرحه. بادئ ذي بدء ، يمكن للمحسن نفسه الاعتماد على UB-ness لتمثيلات المصيدة وإعادة كتابة البرنامج بطرق غير متوقعة. علاوة على ذلك ، لا يدعم Rust حقًا تغيير بيئة FP أيضًا.

لكن يبدو أنه نوع من مسدس القدم ، وأيضًا أحد هؤلاء يضمن أننا لن نكون قادرين على التغيير فعليًا في الممارسة ، لأن الكثير من الناس سيعتمدون عليها في البرية.

أفكار؟

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

¹: فعال بشكل خاص إذا بدأ الناس في استخدام النقابة كرجل فقير متحول لسبب ما.

يجب ألا يكسر من الناحية العملية افتراضات الناس حول استخدام ذاكرة الاتحاد ("ليس أكبر من العضو الأكبر").

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

إحدى المشكلات التي قد ترغب في النظر فيها قبل التثبيت هي https://github.com/rust-lang/rust/issues/37479. يبدو أنه مع أحدث إصدار من اتحادات تصحيح أخطاء LLDB قد لا تعمل :(

alexcrichton هل يعمل مع GDB؟

مما يمكنني قوله ، نعم. يبدو أن روبوتات Linux تجري الاختبار على ما يرام.

ثم هذا يعني أن Rust يوفر جميع معلومات تصحيح الأخطاء الصحيحة ، ولدى LLDB خطأ هنا. لا أعتقد أن خطأ في أحد مصححات الأخطاء المتعددة ، غير موجود في آخر ، يجب أن يمنع تثبيت هذا. يحتاج LLDB فقط إلى الإصلاح.

سيكون من الرائع معرفة ما إذا كان بإمكاننا إدخال هذه الميزة في FCP لدورة 1.17 (هذه النسخة التجريبية في 16 مارس). هل يمكن لأي شخص أن يقدم ملخصًا للأسئلة المعلقة والوضع الحالي للميزة حتى نتمكن من معرفة ما إذا كان بإمكاننا التوصل إلى توافق في الآراء وحل كل شيء؟

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

  • انتظر الإصدار القادم (3 فبراير).
  • اقترح تثبيت النقابات باستخدام حقول Copy . سيغطي هذا جميع احتياجات المؤسسات المالية الأجنبية - ستكون مكتبات المؤسسات المالية الأجنبية قادرة على استخدام النقابات على المستقرة. تُستخدم اتحادات "POD" لعقود في C / C ++ وهي مفهومة جيدًا (أسلوب مستعار يعتمد على النوع المعياري ، لكن Rust لا يحتوي عليه) ، ولا توجد أيضًا مانعات معروفة.
  • اكتب "Unions 1.2" RFC حتى 3 فبراير. وسوف يصف التطبيق الحالي للنقابات ويحدد الاتجاهات المستقبلية. سيتم تحديد مستقبل النقابات ذات الحقول غير Copy في عملية مناقشة RFC هذا.

لاحظ أن الكشف عن شيء مثل ManuallyDrop أو NoDrop من المكتبة القياسية لا يتطلب استقرار النقابات.

تحديث الحالة (4 فبراير): أنا أكتب RFC ، لكنني أواجه كتلة كاتب بعد كل جملة ، كالمعتاد ، لذلك هناك فرصة لأنني أكملها في نهاية الأسبوع المقبل (11-12 فبراير) وليس نهاية هذا الأسبوع (4-5 فبراير).
تحديث الحالة (11 فبراير): النص جاهز بنسبة 95٪ ، وسأرسله غدًا.

petrochenkov الذي يبدو وكأنه مسار عمل معقول للغاية.

petrochenkov هذا يبدو معقولا بالنسبة لي. لقد راجعت أيضًا اقتراح نقاباتك 1.2 وقدمت بعض التعليقات ؛ بشكل عام ، يبدو جيدًا بالنسبة لي.

joshtriplett كنت أفكر في أنه بينما تحدثنا في اجتماع @ rust-lang / lang عن تحديث قوائم

لذلك ، قمت بإرسال طلب المراجعة "الاتحادات 1.2" - https://github.com/rust-lang/rfcs/pull/1897.

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

لا يخبر نص RFC "Unions 1.2" شيئًا جديدًا عن النقابات على غرار FFI ، باستثناء أنه يؤكد صراحة أن نوع المعاقب مسموح به.
تحرير : "Unions 1.2" RFC ستقوم أيضًا بتعيين تعيينات إلى Copy الحقول القابلة للتدمير بشكل تافه (راجع https://github.com/rust-lang/rust/issues/32836#issuecomment-281296416 ، https : //github.com/rust-lang/rust/issues/32836#issuecomment-281748451) ، وهذا يؤثر أيضًا على النقابات على غرار المؤسسات المالية الأجنبية.

يوفر هذا النص أيضًا الوثائق اللازمة لتحقيق الاستقرار.
يمكن نسخ قسم "نظرة عامة" في الكتاب و "التصميم التفصيلي" في المرجع.

بينغ nikomatsakis

هل يجب إضافة شيء كهذا كجزء من اللغة؟ استغرق الأمر مني حوالي 20 دقيقة لبدء تنفيذ اتحاد باستخدام القليل من unsafe و ptr::write() .

use std::mem;
use std::ptr;


/// A union of `f64`, `bool`, and `i32`.
#[derive(Default, Clone, PartialEq, Debug)]
struct Union {
    data: [u8; 8],
}

impl Union {
    pub unsafe fn get<T>(&self) -> &T {
        &*(&self.data as *const _ as *const T)
    }

    pub unsafe fn set<T>(&mut self, value: T) {
        // "transmute" our pointer to self.data into a &mut T so we can 
        // use ptr::write()
        let data_ptr: &mut T = &mut *(&mut self.data as *mut _ as *mut T);
        ptr::write(data_ptr, value);
    }
}


fn main() {
    let mut u = Union::default();
    println!("data: {0:?} ({0:#p})", &u.data);
    {
        let as_i32: &i32 = unsafe { u.get() };
        println!("as i32: {0:?} ({0:#p})", as_i32);
    }

    unsafe {
        u.set::<f64>(3.14);
    }

    println!("As an f64: {:?}", unsafe { u.get::<f64>() });
}

أشعر أنه لن يكون من الصعب على شخص ما كتابة ماكرو يمكنه إنشاء شيء من هذا القبيل باستثناء التأكد من أن المصفوفة الداخلية بحجم أكبر نوع. بعد ذلك ، بدلاً من أن تكون عامة تمامًا (وغير آمنة بشكل مخيف) get::<T>() يمكنهم إضافة سمة مرتبطة بالحد من الأنواع التي يمكنك الحصول عليها وتعيينها. يمكنك أيضًا إضافة طرق معينة لـ getter و setter إذا كنت تريد الحقول المسماة.

أعتقد أنهم قد يكتبون شيئًا مثل هذا:

union! { Foo(u64, Vec<u8>, String) };

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

@ Michael-F-Bryan ليس لدينا size_of ثابت حتى الآن.

@ Michael-F-Bryan لا يكفي أن يكون لديك مصفوفة [u8] ، بل تحتاج أيضًا إلى تصحيح المحاذاة . أنا في الواقع أستخدم بالفعل وحدات الماكرو للتعامل مع النقابات ولكن بسبب عدم وجود ثابت size_of و align_of يجب علي تخصيص المساحة الصحيحة يدويًا ، بالإضافة إلى عدم وجود تسلسل معرف قابل للاستخدام في وحدات الماكرو التعريفي I يجب أن تحدد يدويًا أسماء كل من المحسّنين والمحدّدين. حتى مجرد تهيئة الاتحاد أمر صعب في الوقت الحالي لأنه يتعين علي أولاً تهيئته ببعض القيمة الافتراضية ثم تعيين القيمة إلى المتغير الذي أريده (أو إضافة مجموعة أخرى من الأساليب لإنشاء الاتحاد الذي يكون أكثر إسهابًا في التعريف من الاتحاد). إنه بشكل عام الكثير من العمل وعرضة للخطأ وأقبح من الدعم المحلي للنقابات. ربما يجب عليك قراءة RFC والمناقشة التي رافقته حتى تتمكن من فهم سبب أهمية هذه الميزة.

ونفس الشيء بالنسبة للمحاذاة.

أتخيل أن تسلسل الهوية لا يجب أن يكون صعبًا للغاية الآن syn موجود. يسمح لك بإجراء عمليات على AST الذي تم تمريره ، لذلك يمكنك أن تأخذ معرّفين ، واستخراج تمثيل السلسلة ( Ident ينفذ AsRef<str> ) ، ثم أنشئ Ident جديدًا هو تسلسل الاثنين باستخدام Ident::From<String>() .

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

على سبيل المثال ، يمكن أن يكون لديك MyUnion::default() الذي يساوي صفرًا فقط المخزن المؤقت الداخلي للاتحاد ، ثم fn MyUnion::new<T>(value:T) -> MyUnion ، حيث يحتوي T على سمة محددة تضمن أنه لا يمكنك التهيئة إلا باستخدام الأنواع الصحيحة .

من حيث المحاذاة والحجم ، هل يمكنك استخدام الوحدة النمطية mem من المكتبة القياسية (مثل std :: mem :: align_of () والأصدقاء)؟ أعتقد أن كل شيء أقترحه سيعتمد على القدرة على استخدام تلك الموجودة في وقت التوسع الكلي لمعرفة الحجم والمحاذاة المطلوبة. 99.9٪ من المرات التي يتم فيها استخدام النقابات يتم إجراؤها مع الأنواع البدائية على أي حال ، لذلك أشعر أنك ستكون قادرًا على كتابة وظيفة مساعدة تأخذ اسم النوع وتعيد محاذاة أو حجمه (ربما تطلب من المترجم ، على الرغم من أن هذا أكثر من تفاصيل التنفيذ).

أعترف أن مطابقة الأنماط المضمنة ستكون لطيفة جدًا ، ولكن في معظم الأوقات ، يتم تغليف أي نقابات تستخدمها في FFI في طبقة تجريد رقيقة على أي حال. لذلك قد تكون قادرًا على التخلص من عبارات if / else أو استخدام دالة مساعدة.

من حيث المحاذاة والحجم ، هل يمكنك استخدام وحدة mem من المكتبة القياسية (مثل std :: mem :: align_of () والأصدقاء)؟

لن ينجح ذلك في أي سياق تجميع متقاطع.

@ Michael-F-Bryan كل هذه المناقشات وغيرها الكثير كانت في تاريخ https://github.com/rust-lang/rfcs/pull/1444 . لتلخيص الردود على مخاوفك المحددة ، بالإضافة إلى تلك التي سبق ذكرها: يجب عليك إعادة تنفيذ قواعد الحشو والمحاذاة لكل منصة / مترجم مستهدف ، واستخدام صياغة محرجة في جميع أنحاء كود FFI الخاص بك (وهو ما

أيضا:

99.9٪ من مرات استخدام النقابات يتم استخدام أنواع بدائية على أي حال

ليس صحيحا على الإطلاق. يستخدم كود C على نطاق واسع نمط "هيكل اتحادات البنى" ، حيث تتكون معظم حقول الاتحاد من أنواع هياكل مختلفة.

دمج rfcbot fcp مع تعليقpetrochenkov https://github.com/rust-lang/rust/issues/32836#issuecomment -279256434

ليس لدي ما أضيفه ، فقط تشغيل الروبوت

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

  • [x]aturon
  • [x]eddyb
  • [x]nikomatsakis
  • [x]nrc
  • [x]pnkfelix
  • [x] @ بدون قوارب

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

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

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

PSA: سأقوم بتحديث RFC "Unions 1.2" مع تغيير واحد آخر يؤثر على النقابات على غرار FFI - سأقوم بنقل المهام الآمنة إلى حقول النقابات غير القابلة للتدمير بشكل تافه من "الاتجاهات المستقبلية" إلى RFC المناسبة.

union.trivially_destructible_field = 10; // safe

لماذا ا:

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

petrochenkov هل تقصد "حقول الاتحاد غير

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

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

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

تضمين التغريدة
"حقول قابلة للتدمير بشكل تافه" ، لقد قمت بتعديل الصياغة.

هل تقترح أن كل السلوك غير الآمن يحدث عند القراءة ، حيث تختار تفسيرًا؟

نعم. الكتابة وحدها لا يمكن أن تسبب أي شيء خطير دون قراءة لاحقة.

تعديل:

أشعر أن جهودنا لتقديم فكرة "الحد الأدنى" لما هو غير آمن (مجرد مؤشرات مرجعية) كانت خطأ ، في وقت لاحق

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

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

nrc
حسنًا ، المجموعة الفرعية واضحة إلى حد ما - "اتحادات FFI" ، أو "اتحادات C" ، أو "اتحادات ما قبل C ++ 11" - حتى لو لم تكن نحوية. كان هدفي الأولي هو تثبيت هذه المجموعة الفرعية في أسرع وقت ممكن (هذه الدورة ، بشكل مثالي) بحيث يمكن استخدامها في مكتبات مثل winapi .
لا يوجد شيء مشكوك فيه بشكل خاص حول المجموعة الفرعية المتبقية وتنفيذها ، فهي ليست ملحة وتحتاج إلى الانتظار لفترة غير واضحة من الوقت حتى تكتمل عملية RFC "Unions 1.2". ستكون توقعاتي هي تثبيت الأجزاء المتبقية في 1 أو 2 أو 3 دورات بعد تثبيت المجموعة الفرعية الأولية.

أعتقد أن لدي حجة نهائية بشأن المهام الميدانية الآمنة.
تعيين مجال غير آمن

unsafe {
    u.trivially_destructible_field = value;
}

يعادل التنازل النقابي الكامل الآمن

u = U { trivially_destructible_field: value };

باستثناء أن الإصدار الآمن أقل أمانًا للمفارقة لأنه سيحل محل بايتات u خارج trivially_destructible_field مع undefs ، في حين أن التعيين الميداني يضمن تركها سليمة.

petrochenkov الحد الأقصى لهذا هو size_of_val(&value) == 0 ، أليس كذلك؟

يكون التكافؤ بين المقتطفين صحيحًا فقط إذا كان الحقل المعني كذلك
"تافهة التدمير" ، أليس كذلك؟

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

في 22 شباط (فبراير) 2017 الساعة 14:50 ، إشعارات "فاديم بتروشينكوف"github.com
كتب:

أعتقد أن لدي حجة نهائية بشأن المهام الميدانية الآمنة.
تعيين مجال غير آمن

غير آمن {
u.trivially_destructible_field = القيمة ؛
}

يعادل التنازل النقابي الكامل الآمن

u = U {trivially_destructible_field: value} ؛

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

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment-281660298 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AApc0lUOXLU5xNTfM5PEfEz9nutMZhXUks5rfC8UgaJpZM4IDXsj
.

eddyb

أقصى ما يمكن هو size_of_val (& value) == 0 ، أليس كذلك؟

نعم.

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

petrochenkov يرجى النظر في مثل هذه الحالة:

// Somebody Somewhere in some crate (v 1.0.0)
struct Peach; // trivially destructible
union Banana { pub actually: Peach }

// Somebody Else in their dependent crate
extern some crate;
fn somefn(banana: &mut Banana) {
    banana.actually = Peach;
}

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

impl Drop for Peach { fn drop(&mut self) { println!("Moi Peach!") }

وإصدار 1.1.0 (semver متوافق مع 1.0.0 AFAIK) إصدار من الصندوق.

فجأة لم يعد صندوق السيد Somebody Else يجمع:

fn somefn(banana: &mut Banana) {
    banana.actually = Peach; // ERROR: Something something… unsafe assingment… somewhat somewhat trivially indestructible… 
}

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


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

تضمين التغريدة
هذه حجة جيدة ، لم أفكر في التوافق.

لكن يبدو أن المشكلة قابلة للحل. لتجنب مشاكل التوافق ، افعل نفس الشيء مثل التماسك - تجنب التفكير السلبي. أي استبدل "trivially-destructionable" == "لا توجد مكونات تنفذ Drop " بأقرب تقريب إيجابي - "تنفذ Copy ".
لا يمكن للنوع Copy إلغاء تنفيذ Copy المتوافق مع الإصدارات السابقة ، ولا تزال أنواع Copy تمثل غالبية الأنواع "غير القابلة للتدمير بسهولة" ، خاصة في سياق اتحادات المؤسسات المالية الأجنبية.

تنفيذ Drop غير متوافق بالفعل مع الإصدارات السابقة ولا علاقة لهذا بميزة الاتحاد:

// Somebody Somewhere in some crate (v 1.0.0)
struct Apple; // trivially destructible
struct Pineapple { pub actually: Apple }

// Somebody Else in their dependent crate
extern some crate;
fn pineapple_to_apple(pineapple: Pineapple) -> Apple {
    pineapple.actually
}
// some crate v 1.1.0
impl Drop for Pineapple { fn drop(&mut self) { println!("Moi Pineapple!") }
fn pineapple_to_apple(pineapple: Pineapple) -> Apple {
    pineapple.actually // ERROR: can't move out of Pineapple
}

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

في الأربعاء 22 فبراير 2017 الساعة 10:11 صباحًا ، كتب jethrogb [email protected] :

تطبيق Droping ليس متوافقًا بالفعل مع الإصدارات السابقة وهذا له
لا علاقة له بميزة الاتحاد:

// شخص ما في مكان ما في صندوق ما (الإصدار 1.0.0)
هيكل التفاح // قابل للتدمير بشكل تافه
Struct Pineapple {pub actually: Apple}

// شخص آخر في صندوقهم التابع
خارج بعض قفص
fn pineapple_to_apple (الأناناس: الأناناس) -> التفاح {
الأناناس
}

// بعض الصناديق الإصدار 1.1.0
impl Drop لـ Pineapple {fn drop (& mut self) {println! ("Moi Pineapple!")}

fn pineapple_to_apple (الأناناس: الأناناس) -> التفاح {
banana.actually // خطأ: لا يمكن الخروج من الأناناس
}

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32836#issuecomment-281752949 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABxXhgbFgRNzYOsU4c6Gu1KFfwdjDHn3ks5rfHpYgaJpZM4IDXsj
.

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

petrochenkov أعتبر أنه ربما حجة أن إنشاء نقابة يجب أن يكون غير آمن :)

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

تضمين التغريدة
ما افترضته هو التوقف عن طلب #[feature(untagged_unions)] لهذه المجموعة الفرعية ، وعدم وجود ميزات جديدة أو بيروقراطية أخرى.
من المفترض أن تكون النقابات ذات النمط FFI هي النوع الأكثر استخدامًا من النقابات ، لذا فإن الميزة الجديدة ستعني الكسر المضمون قبل التثبيت مباشرة ، وهو ما أفترض أنه سيكون مزعجًا.

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

يارب
التعبئة؟ إذا كنت تقصد #[repr(packed)] فهذا يعني أنه مدعوم في النقابات الآن (على عكس سمات align(>1) ).

petrochenkov #[repr(packed(N))] . هناك حاجة إلى تعبئة بخلاف 1 في Winapi. ليس الأمر أنني بحاجة إلى دعم هذه الأشياء في النقابات على وجه التحديد ، فأنا لا أريد الانتقال إلى إصدار رئيسي جديد لزيادة الحد الأدنى من متطلبات الصدأ إلا إذا كان بإمكاني الحصول على كل هذه الأشياء في نفس الوقت.

لتوضيح حالة اللعب قليلاً هنا:

اقتراح FCP الحالي هو للنقابات الخالصة- Copy فقط. هذا هو الحال ، على حد علمي ، في الأساس مع عدم وجود أسئلة معلقة بخلاف "هل يجب أن نستقر؟" كان النقاش منذ تقديم الاقتراح إلى FCP حول RFC الجديد لـ petrochenkov .

nrc و nikomatsakis ، من المناقشة على IRC ، أظن

: bell: هذا يدخل الآن فترة التعليق النهائية ، وفقًا للمراجعة أعلاه . :جرس:

تضمين التغريدة
يبدو لي أن هذا سيستفيد من فكرة طرحتها مؤخرًا. (https://internals.rust-lang.org/t/automatic-marker-trait-for-unconditionally-valid-repr-c-types/5054)

على الرغم من عدم اقتراحها مع وضع النقابات في الاعتبار ، إلا أن السمة Plain كما هو موضح (تخضع لتساقط الدراجات) ستسمح باستخدام أي اتحاد يتكون من أنواع Plain دون أي عدم أمان. إنه يرمز للخاصية التي تفيد بأن نمط _ أي _ بت في الذاكرة صالح بشكل متساوٍ ، لذلك يمكنك حل مشكلات التهيئة عن طريق إلزام الذاكرة بالتخلص من الصفر ، ويضمن أن القراءات لا يمكنها استدعاء UB.

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

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

@ le-jzr
يبدو هذا متعامدًا بدرجة كافية مع النقابات.
أقدر قبول Plain في Rust في المستقبل القريب لأنه غير محتمل جدًا + جعل المزيد من الوصول إلى حقول الاتحاد آمنًا إلى حد ما متوافق مع الإصدارات السابقة (ليس بسبب الوبر بالكامل) ، لذلك لن أؤخر النقابات بسبب إليها.

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

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

تحرير: حاول توضيح ما أقصد قوله.

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

فترة التعليق الأخيرة قد اكتملت الآن.

الآن وقد اكتمل دمج FCP ، ما هي الخطوة التالية هنا؟ سيكون من الجيد تثبيت هذا على 1.19.

سيكون من الرائع أن يضيف شخص ما المزيد من التفاصيل إلى رسالة rfcbot ( رمز المصدر هنا ). يمكن أن يسهل على شخص ليس على دراية بالعملية القفز وتحريك الأشياء.

الطريق واضح للحصول على هذا في 1.19. أي شخص على الخطاف لذلك؟ cc joshtriplett

هل تحسين تخطيط التعداد NonZero مضمون للتطبيق من خلال union ؟ على سبيل المثال ، يجب ألا يمثل Option<ManuallyDrop<&u32>> None كمؤشر فارغ. يجب ألا يقرأ Some(ManuallyDrop::new(uninitialized::<[Vec<Foo>; 10]>())).is_some() ذاكرة غير مهيأة.

https://crates.io/crates/nodrop (المستخدم في https://crates.io/crates/arrayvec) لديه اختراقات للتعامل مع هذا.

تضمين التغريدة
تم وضع علامة على هذا حاليًا على أنه سؤال لم يتم حله في RFC .
في التنفيذ الحالي لهذا البرنامج

#![feature(untagged_unions)]

struct S {
    _a: &'static u8
}
union U {
    _a: &'static u8
}

fn main() {
    use std::mem::size_of;
    println!("struct {}", size_of::<S>());
    println!("optional struct {}", size_of::<Option<S>>());
    println!("union {}", size_of::<U>());
    println!("optional union {}", size_of::<Option<U>>());
}

مطبوعات

struct 8
optional struct 8
union 8
optional union 16

، أي لا يتم تنفيذ التحسين.
سم مكعب https://github.com/rust-lang/rust/issues/36394

من غير المحتمل أن يحقق هذا 1.19.

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

من غير المحتمل أن يحقق هذا 1.19.

تم دمج PR الاستقرار .

مع شحن النقابات غير المميزة بعلامات الآن في 1.19 (جزئيًا من https://github.com/rust-lang/rust/pull/42068) - هل هناك أي شيء متبقي حول هذه المشكلة أم يجب إغلاقها؟

تضمين التغريدة
لا يزال هناك عالم كامل من النقابات بحقول غير Copy !
يتم حظر التقدم في الغالب على التوضيح / التوثيق RFC (https://github.com/rust-lang/rfcs/pull/1897).

هل كان هناك أي تقدم في النقابات ذات الحقول غير Copy منذ أغسطس؟ يبدو أن طلب التعليقات الخاص بالاتحادات 1.2 متوقفًا (أظن أنه بسبب فترة الضمانات؟)

السماح بأنواع ?Sized في النقابات - فقط للنقابات من نوع واحد - سيجعل من السهل تنفيذ https://github.com/rust-lang/rust/issues/47034 :

`` صدأ
الاتحاد يدويا{
القيمة: ت
}

mikeyhew ما عليك سوى أن تطلب عدم حجم نوع واحد على الأكثر.

أنا أنظر إلى بعض أكواد Rust باستخدام union s ، وليس لدي أي فكرة عما إذا كانت تستدعي سلوكًا غير محدد أم لا.

يذكر المرجع [العناصر :: الاتحادات] فقط:

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

لكن لا يمكنني العثور على تعريف "متوافق مع التخطيط" لا في [items :: unions] ولا في [type_system :: type_layout] .

بالنظر إلى RFCs ، لم أتمكن من العثور على تعريف "متوافق مع التخطيط" أيضًا ، فقط أمثلة موجزة يدويًا لما يجب وما لا يجب أن يعمل في RFC 1897: Unions 1.2 (غير مدمج).

RFC1444: يبدو أن

هل القواعد _ الدقيقة _ التي تخبرني ما إذا كان جزء من التعليمات البرمجية يستخدم النقابات قد حدد سلوكًا مكتوبًا في مكان ما (وما هو السلوك المحدد)؟

gnzlbg إلى

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

إلى التقدير التقريبي الأول: لا يجوز لك الوصول إلى الحشو ، ولا يجوز لك الوصول إلى تعداد يحتوي على مميّز غير صالح ، ولا يجوز لك الوصول إلى منطقي يحتوي على قيمة غير صحيحة أو خاطئة ، ولا يجوز لك الوصول إلى قيمة فاصلة عائمة غير صالحة أو تشير ، و بعض الأشياء الأخرى مثل تلك.

في الواقع ، الإجماع الأخير هو أن قراءة تعويم عشوائي أمر جيد (# 46012).

أود أن أضيف متطلبًا آخر: كل من متغيرات الاتحاد المصدر والهدف هي #[repr(C)] وكذلك جميع حقولهم (وبشكل متكرر) إذا كانت هياكل.

Amanieu أقف مصححة ، شكرا لك.

لذا أعتقد أن القواعد ليست مكتوبة في أي مكان؟

أنا أبحث في كيفية استخدام stdsimd مع واجهته الجديدة. ما لم نثبت بعض الإضافات بها ، سيحتاج المرء إلى استخدام النقابات للقيام بالكتابة مع بعض أنواع simd ، مثل هذا:

https://github.com/rust-lang-nursery/stdsimd/blob/03cb92ddce074a5170ed5e5c5c20e5fa4e4846c3/coresimd/src/x86/test.rs#L17

كتابة AFAIK لحقل نقابي وقراءة حقل آخر يشبه إلى حد كبير استخدام transmute_copy ، مع نفس القيود. كون هذه القيود لا تزال غامضة بعض الشيء لا يتعلق بالاتحاد.

لهذه المسألة ، يمكن أن تستخدم الوظيفة التي ربطتها transmute::<__m128d, [f64; 2]> . على الرغم من أن النسخة الموحدة هي أجمل ، إلا أنه بمجرد إزالة التحويل الموجود حاليًا على الأقل: يمكن أن يكون فقط A { a }.b[idx] .

rkruppe لقد ملأت مشكلة clippy لإضافة هذا الوبر: https://github.com/rust-lang-nursery/rust-clippy/issues/2361

يمكن للوظيفة التي ربطتها فقط استخدام الإرسال :: <__ m128d i = "8">

أعتقد أن القواعد التي أبحث عنها هي متى يستدعي التحويل سلوكًا غير محدد بعد ذلك (لذلك سأبحث عن هؤلاء).

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

لكي نكون واضحين ، فإن التحويل [_نسخ] ليس "أكثر بدائية" من النقابات. في الواقع ، يعد transmute_copy حرفياً مجرد مؤشر as casts بالإضافة إلى ptr::read . يحتاج transmute أيضًا إلى mem::uninitialized (مهمل) أو MaybeUninitialized (اتحاد) أو شيء من هذا القبيل ، ويتم تنفيذه باعتباره جوهريًا للكفاءة ، ولكنه يتلخص أيضًا في نوع معاقبة memcpy. السبب الرئيسي الذي جعلني أرسم الاتصال بالنقل هو أنه قديم ومُبالغ فيه تاريخيًا ، وبالتالي لدينا حاليًا المزيد من عمليات الكتابة والمعرفة الفولكلورية التي تركز على التحويل على وجه التحديد. المفهوم الأساسي الحقيقي ، الذي يحدد ما هو صالح وما هو غير صالح (والذي ستصفه المواصفات) ، هو كيفية تخزين القيم في الذاكرة على هيئة بايت وأي تسلسلات بايت هي UB لقراءتها على أنها أنواع.

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

fn transmute<T, U>(x: T) -> U {
    assert!(size_of::<T>() == size_of::<U>());
    let mut bytes = [0u8; size_of::<U>()];
    ptr::write(bytes.as_mut_ptr() as *mut T, x);
    mem::forget(x);
    ptr::read(bytes.as_ptr() as *const U)
}

الجزء "السحري" الوحيد من التحويل هو أنه يمكنه تقييد معلمات النوع لتكون ذات حجم متساوٍ في وقت الترجمة .

المرجع والنقابات 1.2 RFC غامضة عن قصد بشأن هذه المسألة لأن قواعد التحويل بشكل عام لم يتم تحديدها.
كان القصد "من أجل اتحادات repr(C) انظر مواصفات ABI للجهات الخارجية ، بالنسبة لتوافق تخطيط الاتحادات repr(Rust) غير محدد في الغالب (ما لم يكن كذلك)".

هل فات الأوان لإعادة النظر في دلالات التحقق من الإسقاط للنقابات ذات الحقول المنخفضة؟

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

يوجد مثال تم تجريده في https://play.rust-lang.org/؟gist=607e2dfbd51f4062b9dc93d149815695&version=nightly. الفكرة هي أن هناك نوعًا Pin<'a, T> ، مع طريقة pin(&'a self) -> &'a T تعتمد سلامتها على الثابت "بعد استدعاء pin.pin() ، إذا تم استعادة الذاكرة التي تدعم الدبوس ، إذن يجب تشغيل أداة تدمير الدبوس ".

تم الحفاظ على هذا الثابت بواسطة Rust حتى تمت إضافة #[allow(unions_with_drop_fields)] ، واستخدامه بواسطة ManuallyDrop https://doc.rust-lang.org/src/core/mem.rs.html#949.

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

محادثة IRC: https://botbot.me/mozilla/rust-lang/2018-02-01/؟msg=96386869&page=3

قضية جوزفين: https://github.com/asajeffrey/josephine/issues/52

نسخة إلى:noxeddybpnkfelix

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

المدمرات ليست مضمونة للتشغيل. الصدأ لا يضمن ذلك. إنه يحاول ، ولكن ، على سبيل المثال ، تم تحويل std::mem::forget إلى وظيفة آمنة.

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

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

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

تحرير: عفوًا ، لم أقصد النقر على "إغلاق والتعليق".

joshtriplett نعم ، لا يضمن Rust أن mem::forget ، حيث لا يمكنك تسميته بقيمة مستعارة من بيرما.

هذا هو ما كانت تعتمد عليه Joephine بشكل سيء ، لكنه لم يعد صحيحًا بسبب الطريقة التي يعامل بها المدقق unions_with_drop_fields .

سيكون من الجيد إذا تم اعتبار allow(unions_with_drop_fields) تعليقًا توضيحيًا غير آمن ، فلن يكون هذا تغييرًا جذريًا ، AFAICT ، سيتطلب فقط deny(unsafe_code) للتحقق من allow(unions_with_drop_fields) .

asajeffrey ما زلت أحاول فهم الشيء Pin ... لذا ، إذا اتبعت المثال بشكل صحيح ، فإن السبب في أن هذا "يعمل" هو أن fn pin(&'a Pin<'a, T>) -> &'a T يجبر الاقتراض على الاستمرار لمدة طالما تم التعليق على العمر 'a في النوع ، وهذه المدة ثابتة علاوة على ذلك.

هذه ملاحظة مثيرة للاهتمام! لم أكن على علم بهذه الحيلة. شعوري الداخلي هو أن هذا يعمل "عن طريق الصدفة" ، أي أن الصدأ الآمن لا يحدث لتوفير وسيلة لمنع المدمر من الركض ولكن هذا لا يجعل هذا جزءًا من "العقد". والجدير بالذكر أن https://doc.rust-lang.org/nightly/reference/behavior-consoded-undefined.html لا يسرد التسريبات.

IMO لا يهم إذا كان يعمل عن طريق الصدفة أو عن قصد. لم تكن هناك طريقة لتجنب تشغيل Drop بهذه الخدعة قبل ManuallyDrop الموجودة (والتي تتطلب رمزًا غير آمن ليتم تنفيذه) ، والآن لا يمكننا الاعتماد على ذلك بعد الآن.

إن إضافة ManuallyDrop قتلت بشكل أساسي هذا السلوك اللطيف لـ Rust والقول بأنه لا ينبغي الاعتماد عليه في المقام الأول يبدو وكأنه تفكير دائري بالنسبة لي. إذا لم يسمح ManuallyDrop بالاتصال Pin::pin ، فهل سيكون هناك أي طريقة أخرى لجعل الاتصال Pin::pin غير سليم؟ لا أعتقد ذلك.

لا أعتقد أننا يمكن أن نلتزم بالحفاظ على كل ضمان يحدث عن طريق الخطأ rustc

أيضًا ، هذا سيف ذو حدين - كل ضمان إضافي نقرر تقديمه هو شيء يجب أن يعتني به رمز غير آمن. لذا فإن اكتشاف ضمان جديد قد يؤدي أيضًا إلى كسر الكود غير الآمن الموجود (الجلوس بصمت في مكان ما على crates.io دون أن يكون على علم) مثل تمكين رمز جديد غير آمن (كان هذا هو الحال هنا).

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

إذا لم يسمح ManuallyDrop باستدعاء Pin :: pin ، فهل سيكون هناك أي طريقة أخرى لجعل استدعاء Pin :: pin غير سليم؟ لا أعتقد ذلك.

مع unsafe كود ، سيكون هناك. لذا بإعلانك هذا الصوت الخادع Pin ، فإنك تعلن عن بعض الكود غير الآمن unsound الذي سيكون سليمًا إذا قررنا ManuallyDrop ما يرام.

ما وصفناه هو طريقة مريحة للغاية لدمج الصدأ مع GCs. ما أحاول قوله هو أنه يبدو لي أنه من الخطأ إخبارنا أن هذا كان مجرد حادث نجح وأنه يجب علينا أن ننساه ، عندما لا أجد أي حالة استخدام لعدم تقييد النقابات بـ Drop الحقول كما وصفها asajeffrey هنا ، وعندما يكون هذا هو الثؤلول الوحيد الذي كسر جوزفين.

سأكون سعيدًا لنسيان الأمر إذا تمكن شخص ما من إظهار أنه غير سليم حتى بدون ManuallyDrop .

ما أحاول قوله هو أنه يبدو من الخطأ بالنسبة لي أن أخبرنا أن هذا كان مجرد حادث كان ناجحًا

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

وأن ننساها

كان يجب أن أوضح أن هذا الجزء هو مجرد شعوري الداخلي. أعتقد أنه قد يكون أيضًا نقطة عمل معقولة للإعلان عن هذا "حادث سعيد" وجعله ضمانًا فعليًا - إذا كنا واثقين بشكل معقول من أن جميع رموز unsafe تحترم هذا الضمان بالفعل ، وأن يعتبر تقديم هذا الضمان أكثر أهمية من حالة استخدام ManuallyDrop . هذه مقايضة ، شبيهة بـ leapocalypse ، حيث لا يمكننا أن نأكل كعكتنا ونحصل عليها أيضًا (لا يمكننا الحصول على كل من Rc مع API الحالي و drop - لا يمكن أن يكون لدينا كل من ManuallyDrop و Pin ) لذلك علينا اتخاذ قرار في كلتا الحالتين.

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

إذا كنا واثقين بشكل معقول من أن جميع رموز unsafe تحترم هذا الضمان بالفعل ، وأن توفير هذا الضمان أهم من حالة الاستخدام ManuallyDrop . هذه مقايضة ، شبيهة بـ leapocalypse ، حيث لا يمكننا أن نأكل كعكتنا ونحصل عليها أيضًا (لا يمكننا الحصول على كل من Rc مع API الحالي و drop - لا يمكن أن يكون لدينا كل من ManuallyDrop و Pin ) لذلك علينا اتخاذ قرار في كلتا الحالتين.

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

بقدر ما أفهم أن اقتراح آلان هو عدم إزالة ManuallyDrop ، فقط لجعل Dropck يفترض أنه (والنقابات الأخرى التي تحتوي على حقول Drop ) لديها مدمر. (لا يفعل المدمرون شيئًا ، لكن مجرد وجوده يؤثر على البرامج التي يقبلها Dropck أو يرفضها).

سأكون سعيدًا لنسيان الأمر إذا تمكن شخص ما من إظهار أنه غير سليم حتى بدون ManuallyDrop.

لست متأكدًا مما إذا كان هذا مؤهلاً أم لا ، ولكن هذه هي محاولتي الأولى: تنفيذ سخيف لشيء مثل ManuallyDrop الذي يعمل في فترة ما قبل الصدأ union .

pub mod manually_drop {
    use std::mem;
    use std::ptr;
    use std::marker::PhantomData;

    pub struct ManuallyDrop<T> {
        data: [u8; 32],
        phantom: PhantomData<T>,
    }

    impl<T> ManuallyDrop<T> {
        pub fn new(x: T) -> ManuallyDrop<T> {
            assert!(mem::size_of::<T>() <= 32);
            let mut data = [0u8; 32];
            unsafe {
                ptr::copy(&x as *const _ as *const u8, &mut data[0] as *mut _, mem::size_of::<T>());
            }
            mem::forget(x);
            ManuallyDrop { data, phantom: PhantomData }
        }

        pub fn deref(&self) -> &T {
            unsafe {
                &*(&self.data as *const _ as *const T)
            }
        }
    }
}

(نعم ربما يتعين علي القيام ببعض العمل الإضافي للحصول على المحاذاة الصحيحة ، ولكن يمكن القيام بذلك أيضًا من خلال التضحية ببعض البايت.)
ملعب يظهر هذا كسر Pin : https://play.rust-lang.org/؟gist=fe1d841cedb13d45add032b4aae6321e&version=nightly

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

الآن هذا مثير للاهتمام. في بعض إصدارات عناصر التثبيت الخاصة بنا ، يأخذ Pin::pin &'this mut Pin<'this, T> ، لكن لن يكون من غير المعقول أن يكون لديك ManuallyDrop أن يكون لديك DerefMut ، صحيح ؟

هنا ملعب يُظهر أن RalfJung (بشكل غير مفاجئ) لا يزال يكسر Pin مع طريقة &mut -taking pin .

https://play.rust-lang.org/؟gist=5057570b54952e245fa463f8d7719663&version=nightly

لن يكون من غير المعقول أن يكون لـ ManuallyDrop الخاص بك إشارة ضمنية DerefMut ، أليس كذلك؟

نعم ، لقد أضفت للتو API الذي أحتاجه لهذا المثال. يجب أن يعمل deref_mut الواضح بشكل جيد.

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

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

    unsafe impl<#[may_dangle] T> Drop for ManuallyDrop<T> {
        fn drop(&mut self) {}
    }

فقط إذا قمت بإزالة #[may_dangle] Rust يرفضه. لذلك ، على الأقل ، يجب أن نخرج ببعض القواعد التي تنتهك الشفرة أعلاه - مجرد قول "هناك بعض التعليمات البرمجية التي نريد أن نكون على ما يرام بها" هي مكالمة سيئة لأنها تجعلها من المستحيل إلى حد كبير إلقاء نظرة على بعض التعليمات البرمجية والتحقق مما إذا كانت سليمة.


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

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

المناطق الزمنية ممتعة ، لقد استيقظت للتو! يبدو أن هناك مشكلتين هنا (الثوابت بشكل عام ، و dropck بشكل خاص) ، لذلك سأضعهما في تعليقات منفصلة ...

RalfJung : نعم ، هذه مشكلة تتعلق I الثابت والذي يتم صيانته باستخدام منطق ضمان الاعتماد. وبالفعل ، قد يكون هناك مكتبتان L1 و L2 ، اختارتا غير متوافقين I1 و I2 ، مثل Rust + L1 آمن و الصدأ + L2 آمن ، لكن الصدأ + L1 + L2 غير آمن.

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

ومن المثير للاهتمام ، أن الإرشادات الموجودة على https://doc.rust-lang.org/nightly/reference/behavior-consoded-undefined.html مكتوبة بالشكل "تقع على عاتق المبرمج عند كتابة تعليمات برمجية غير آمنة عدم إمكانية السماح بتعليمة برمجية آمنة تعرض هذه السلوكيات: ... "أي ، إنها خاصية سياقية (بالنسبة لجميع السياقات الآمنة ، لا يمكن أن يحدث خطأ C و C [P]) وبالتالي فهي تعتمد على الإصدار (نظرًا لأن الإصدار 1.20 من Rust + std يحتوي على أكثر أمانًا سياقات من الإصدار 1.18). على وجه الخصوص ، كنت أزعم أن التثبيت قد استوفى بالفعل هذا الشرط لـ Rust قبل 1.20 ، نظرًا لعدم وجود سياق آمن C st C [Pinning] يحدث خطأ.

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

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

الشيء المحدد الذي فعلته النقابات غير المميزة (وبالتالي ManuallyDrop ) كان في التفاعل مع مدقق الإسقاط ، على وجه الخصوص ، يتصرف ManualDrop مثل defn الخاص به:

unsafe impl<#[may_dangle] T> Drop for ManuallyDrop<T> { ... }

وبعد ذلك يمكنك إجراء محادثة حول ما إذا كان هذا مسموحًا به أم لا :) في الواقع ، تحدث هذه المحادثة في سلسلة may_dangle بدءًا من https://github.com/rust-lang/rust/issues/ 34761 # issuecomment -362375924

RalfJung يظهر data هو T ، لكن نوع وقت الترجمة هو [u8; N] . ما النوع الذي يتم حسابه بقدر ما يتعلق الأمر بـ may_dangle ؟

ومن المثير للاهتمام ، أن الإرشادات الموجودة على https://doc.rust-lang.org/nightly/reference/behavior-consoded-undefined.html مكتوبة بالشكل "تقع على عاتق المبرمج عند كتابة تعليمات برمجية غير آمنة عدم إمكانية السماح بتعليمة برمجية آمنة تعرض هذه السلوكيات: ... "أي ، إنها خاصية سياقية

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

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

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

على سبيل المثال ، حدود السلوك غير المحدد وما يمكن أن يفعله الرمز غير الآمن ليست هي نفسها. راجع https://github.com/nikomatsakis/rust-memory-model/issues/44 للحصول على مناقشة حديثة حول هذا الموضوع: لا يؤدي تكرار &mut T مقابل mem::size_of::<T>() == 0 إلى أي سلوك غير محدد بشكل مباشر ، ومع ذلك فمن الواضح أنه يعتبر غير قانوني لعمل التعليمات البرمجية غير الآمنة. والسبب هو أن التعليمات البرمجية الأخرى غير الآمنة قد تعتمد على احترام نظام الملكية الخاص بها ، وتكرار الأشياء ينتهك هذا النظام.

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

أوه ، هذا بالتأكيد. وأتساءل ما الذي يمكننا فعله لتجنب ذلك في المستقبل؟ ربما تضع بعض التحذيرات الكبيرة على https://doc.rust-lang.org/nightly/reference/behavior-consoded-undefined.html قائلة "لمجرد حدوث عنصر ثابت في rustc + libstd ، لا يعني أن الشفرة غير الآمنة يمكن الاعتماد عليها ؛ بدلاً من ذلك ، إليك بعض الثوابت التي يمكنك الاعتماد عليها "؟

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

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

الفحص المستمر حاليًا لا يحتوي على حقول اتحاد ذات حالة خاصة: ( ccsolson @ oli-obk)

union Transmute<T, U> { from: T, to: U }

const SILLY: () = unsafe {
    (Transmute::<usize, Box<String>> { from: 1 }.to, ()).1
};

fn main() {
    SILLY
}

ينتج عن الكود أعلاه خطأ تقييم ميري "استدعاء غير ثابت fn std::ptr::drop_in_place::<(std::boxed::Box<std::string::String>, ())> - shim(Some((std::boxed::Box<std::string::String>, ()))) ".

تغييره لفرض نوع .to ليتم ملاحظته من قبل مدقق const:

const fn id<T>(x: T) -> T { x }

const SILLY: () = unsafe {
    (id(Transmute::<usize, Box<String>> { from: 1 }.to), ()).1
};

النتائج في "لا يمكن تقييم المدمرات في وقت الترجمة".

رمز التنفيذ ذي الصلة هنا (تحديدًا restrict call):
https://github.com/rust-lang/rust/blob/5e4603f99066eaf2c1cf19ac3afbac9057b1e177/src/librustc_mir/transform/qualify_consts.rs#L557

كشف تحليل أفضل لـ # 41073 أن الدلالات الخاصة بوقت تشغيل المدمرات عند التخصيص لحقول فرعية للنقابات ليست جاهزة بشكل كافٍ لتحقيق الاستقرار. راجع هذه القضية للحصول على التفاصيل.

هل من الواقعي استبعاد أنواع Drop في النقابات ، وتنفيذ ManuallyDrop بشكل منفصل (كعنصر lang)؟ مما يمكنني قوله ، يبدو أن ManuallyDrop هو الدافع الأكبر ل Drop في النقابات ، لكن هذه حالة خاصة جدًا.

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


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

يتعارض هذا مع الطريقة التي تُستخدم بها النقابات أحيانًا في لغة C ، وهي "نقطة امتداد" (كان IIRCjoshtriplett هو من ذكر ذلك للجميع): قد يعلن ملف الرأس عن 3 متغيرات للنقابة ، ولكن هذا يعتبر متوافقًا مع إضافة المزيد من المتغيرات لاحقًا (طالما أن ذلك لا يزيد من حجم الاتحاد). يعد مستخدم المكتبة بعدم لمس بيانات الاتحاد إذا كانت العلامة (الموجودة في مكان آخر) تشير إلى أنهم لا يعرفون المتغير الحالي. الاهم من ذلك إذا كنت تعرف سوى متغير واحد، وهذا لا يعني أن هناك ليست سوى متغير واحد!

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

هذا المسند موجود بالفعل ، ولكنه متحفظ في الأدوية الجنسية بسبب عدم وجود سمة يجب الالتزام بها.
يمكنك الوصول إليه عبر std::mem::needs_drop (والذي يستخدم عنصر جوهري ينفذه rustc ).

eddyb سوف needs_drop التوافق إلى الأمام في الاعتبار ، أم أنه سينظر بسعادة إلى الإنشاءات الأخرى لتحديد ما إذا كانت أنواعها تنفذ Drop ؟ الهدف هنا هو الحصول على فحص لن ينفصل أبدًا عن التغييرات المتوافقة مع semver ، حيث على سبيل المثال إضافة impl Drop إلى بنية بدون نوع أو معلمات مدى الحياة وتكون الحقول الخاصة فقط متوافقة مع semver.

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

يتعارض هذا مع الطريقة التي تُستخدم بها النقابات أحيانًا في لغة C ، وهي "نقطة امتداد" (كان IIRCjoshtriplett هو من ذكر ذلك للجميع): قد يعلن ملف الرأس عن 3 متغيرات للنقابة ، ولكن هذا يعتبر متوافقًا مع إضافة المزيد من المتغيرات لاحقًا (طالما أن ذلك لا يزيد من حجم الاتحاد). يعد مستخدم المكتبة بعدم لمس بيانات الاتحاد إذا كانت العلامة (الموجودة في مكان آخر) تشير إلى أنهم لا يعرفون المتغير الحالي. بشكل حاسم ، إذا كنت تعرف متغيرًا واحدًا فقط ، فهذا لا يعني أنه لا يوجد سوى متغير واحد!

هذه حالة محددة للغاية.
إنه يؤثر فقط على اتحادات النمط C (لذلك لا توجد مدمرات وكل شيء Copy ، بالضبط المجموعة الفرعية المتوفرة على المستقر) التي تم إنشاؤها من رؤوس C.
يمكننا بسهولة إضافة حقل _dummy: () أو _future: () لمثل هذه النقابات والاستمرار في الاستفادة من نموذج "التعداد" الأكثر أمانًا بشكل افتراضي. يعتبر اتحاد المؤسسات المالية الأجنبية "نقطة امتداد" أمرًا يحتاج إلى توثيق جيد على أي حال.

في 17 أبريل 2018 10:08:54 PDT، فاديم Petrochenkov [email protected] كتب:

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

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

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

اتحاد FFI
أن تكون "نقطة امتداد" أمر يحتاج إلى توثيق جيد
على أي حال.

يجب علينا بالتأكيد توثيق الدلالات بأكبر قدر ممكن من الدقة.

RalfJung لا ، يتصرف مثل auto trait s ، ويكشف كل التفاصيل الداخلية.

يوجد حاليًا بعض النقاش حول "الحقول النشطة" وتراجع النقابات على https://github.com/rust-lang/rust/issues/41073#issuecomment -380291471

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

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

بالنسبة لي ، إذا كنت تريد إسقاط محتويات الاتحاد ، فيجب عليك ~ الإرسال / التحويل (ربما لا يمكن تحويله لأنه قد يكون أكبر مع بعض البتات غير المستخدمة في النهاية لمتغير آخر) إلى النوع تريد إسقاط ~ خذ المؤشرات إلى الحقول التي تحتاج إلى الإفلات واستخدم std::ptr::drop_in_place ، أو استخدم بناء جملة الحقل لاستخراج القيمة.

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

مثال - تمثيل mem::uninitialized كاتحاد

pub union MaybeValid<T> {
    valid: T,
    invalid: ()
}

impl<T> MaybeValid<T> {
    #[inline] // this should optimize to a no-op
    pub fn from_valid(valid: T) -> MaybeValid<T> {
        MaybeValid { valid }
    }

    pub fn invalid() -> MaybeValid<T> {
        MaybeValid { invalid: () }
    }

   pub fn zeroed() -> MaybeValid<T> {
        // do whatever is necessary here...
        unimplemented!()
    }
}

fn example() {
    let valid_data = MaybeValid::from_valid(1_u8);
    // Destructor of a union always does nothing, but that's OK since our 
    // data type owns nothing.
    drop(valid_data);
    let invalid_data = MaybeValid::invalid();
    // Destructor of a union again does nothing, which means it needs to know 
    // nothing about its surroundings, and can't accidentally try to free unused memory.
    drop(invalid_data);
    let valid_data = MaybeValid::from_valid(String::from("test string"));
    // Now if we dropped `valid_data` we would leak memory, since the string 
    // would never get freed. This is already possible in safe rust using e.g. `Rc`. 
    // `union` is a similarly advanced feature to `Rc` and so new users are 
    // protected by the order in which concepts are introduced to them. This is 
    // still "safe" even though it leaks because it cannot trigger UB.
    //drop(valid_data)
    // Since we know that our union is of a particular form, we can safely 
    // move the value out, in order to run the destructor. I would expect this 
    // to fail if the drop method had run, even though the drop method does 
    // nothing, because that's the way stuff works in rust - once it's dropped
    // you can't use it.
    let _string_to_drop = unsafe { valid_data.valid };
    // No memory leak and all unsafety is encapsulated.
}

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

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

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

ذات صلة: بالنسبة للثوابت ، أنا أجادل حاليًا بأن حقل واحد على الأقل من الاتحاد داخل ثابت يجب أن يكون صحيحًا: https://github.com/rust-lang/rust/pull/51361 (إذا كان لديك حقل ZST هذا دائما صحيح او صادق)

سأقوم بنشر هذا ثم تحريره حتى لا أفقد عملي.

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

derekdreery (وأي شخص آخر) سأكون مهتمًا بتعليقاتك على https://internals.rust-lang.org/t/pre-rfc-unions-drop-types-and-manuallydrop/8025

ذات صلة: بالنسبة للثوابت ، أجادل حاليًا في أن مجالًا واحدًا على الأقل من الاتحاد داخل ثابت يجب أن يكون صحيحًا: # 51361

لقد رأيت التنفيذ ولكن لم أر الحجة. ؛)

حسنًا ... حجة "عدم التحقق على الإطلاق تبدو غريبة".

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

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

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

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

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

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

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

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

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

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

1 على سبيل المثال ، بافتراض أن المجموعات هي repr (C) للبساطة ، فإن union Foo { a: (bool, u8), b: (u8, bool) } يسمح لك بإنشاء شيء غير صالح فقط عن طريق تعيينات الحقول.

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

union Foo {a: (bool، u8)، b: (u8، bool)}

مرحبًا ، هذا هو المثال الخاص
وهو صالح وفقًا لنموذج RFC 1897 (واحد على الأقل من أجزاء "الورقة" bool -1 ، u8 -1 ، u8 -2 ، bool -2 صالح بعد أي تخصيصات جزئية).

يجب أن تكون نقابات التعامل مع الكود حذرة للغاية في كيفية الكتابة إلى الاتحاد أو المخاطرة بـ UB الفوري

هذا هو الهدف من نموذج RFC 1897 ، يضمن الفحص الثابت أنه لا يمكن لأي عملية آمنة (مثل التعيين أو التعيين الجزئي) أن تحول الاتحاد إلى حالة غير صالحة ، لذلك لا تحتاج إلى توخي الحذر الشديد طوال الوقت وعدم الحصول على UB الفوري .
فقط العمليات غير الآمنة غير المرتبطة بالنقابات مثل عمليات الكتابة من خلال المؤشرات البادئة يمكن أن تجعل الاتحاد غير صالح.

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

let u: Union;
let x = u.field; // UB

هذا هو الهدف من نموذج RFC 1897 ، يضمن الفحص الثابت أنه لا يمكن لأي عملية آمنة (مثل التعيين أو التعيين الجزئي) أن تحول الاتحاد إلى حالة غير صالحة ، لذلك لا تحتاج إلى توخي الحذر الشديد طوال الوقت وعدم الحصول على UB الفوري .
فقط العمليات غير الآمنة غير المرتبطة بالنقابات مثل عمليات الكتابة من خلال المؤشرات البادئة يمكن أن تجعل الاتحاد غير صالح.

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

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

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

تعديل 2 هل يمكن أن تبدأ النقابات كحقيبة من القطع ثم تسمح لاحقًا بالوصول الآمن بينما تكون متوافقة مع الإصدارات السابقة؟

وهي صالحة بموجب نموذج RFC 1897 (واحد على الأقل من أجزاء "الورقة" bool-1، u8-1، u8-2، bool-2 صالح بعد أي تعيينات جزئية).

إذا قررنا أننا نريد أن يكون هذا صالحًا ، أعتقد أنه يجب على @ oli-obk تحديث شيكات miri لتعكس ذلك - مع دمج https://github.com/rust-lang/rust/pull/51361 ، سيتم رفضه بواسطة ميري.

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

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

أعتقد أنني أفتقد شيئًا أساسيًا هنا. أتفق مع rkruppe على ذلك
المشكلة الصعبة مع النقابات هي التأكد من أن النقابة تخزن حاليًا
البيانات التي يريد البرنامج قراءتها.

لكن AFAIK لا يمكن حل هذه المشكلة "محليًا" عن طريق التحليل الثابت. نحن
من شأنه على الأقل قراءة تحليل البرنامج بالكامل ، وحتى ذلك الحين سيظل كذلك
مشكلة صعبة الحل.

إذن ... هل هناك حل لهذه المشكلة على الطاولة؟ أو ماذا يفعل
الحلول الدقيقة المقترحة تشتري لنا بالفعل؟ لنفترض أنني حصلت على اتحاد من C ،
بدون تحليل برنامج Rust and C بأكمله ، ما الذي يمكن أن يقترحه
التحليلات الثابتة في الواقع تضمن للقراء؟

gnzlbg أعتقد أن الضمان الوحيد الذي سنحصل عليه هو ما كتبه petrochenkov أعلاه

يضمن الفحص الثابت أنه لا يمكن لأي عملية آمنة (مثل التعيين أو التعيين الجزئي) تحويل الاتحاد إلى حالة غير صالحة

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

لا يحمي اقتراحك من القراءات السيئة أيضًا ، لا أعتقد أن هذا ممكن.

أيضًا ، تخيلت بعض التتبع "المبدئي" الأساسي جدًا على غرار "الكتابة إلى أي حقل يهيئ الاتحاد". سنحتاج إلى شيء على أي حال عندما يُسمح بـ impl Drop for MyUnion . للأفضل أو للأسوأ ، علينا أن نقرر متى وأين نقوم بإدراج مكالمات الإسقاط التلقائي للنقابة. يجب أن تكون هذه القواعد بسيطة قدر الإمكان لأن هذا رمز إضافي نقوم بإدخاله في الكود الدقيق غير الآمن الحالي. بالنسبة للنقابات التي تنفذ Drop ، تخيلت أيضًا قيدًا مشابهًا لـ struct لا يسمح بالكتابة إلى حقل ما لم تتم تهيئة بنية البيانات بالفعل.

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

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

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

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

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

ربما يجب أن أبدأ من الاتجاه الآخر.

هل ينبغي أن يعمل هذا القانون 1) للنقابات 2) لغير النقابات؟

let x: T;
let y = x.field;

بالنسبة لي ، الإجابة واضحة بـ "لا" في كلتا الحالتين ، لأن هذه فئة كاملة من الأخطاء التي يمكن لراست أن يمنعها ويريد منعها ، بغض النظر عن "الاتحاد" T .

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

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

هذه الحالة من joshtriplett ، على سبيل المثال

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

سيكون أكثر وضوحًا للأشخاص الذين يقرؤون الكود إذا كان لدى الاتحاد حقلاً إضافيًا لـ "الامتدادات المستقبلية المحتملة".

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

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

هل ينبغي أن يعمل هذا القانون 1) للنقابات 2) لغير النقابات؟

let x: T;
let y = x.field;

بالنسبة لي ، الإجابة واضحة بـ "لا" في كلتا الحالتين ، لأن هذه فئة كاملة من الأخطاء التي يمكن لراست أن يمنعها ويريد منعها ، بغض النظر عن "الاتحاد" T .

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

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

تمت الموافقة عليه حتى الآن ، على افتراض أنني أفهم قواعد الهياكل.

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

هذا الشرط الإضافي غير صالح للنقابات.

هذه الحالة من joshtriplett ، على سبيل المثال

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

سيكون أكثر وضوحًا للأشخاص الذين يقرؤون الكود إذا كان لدى الاتحاد حقلاً إضافيًا لـ "الامتدادات المستقبلية المحتملة".

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

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

يعني "الطرف الثالث" غير الآمن "الحصول على اتحاد من FFI" ، وهي حالة استخدام صالحة تمامًا.

إليك مثال ملموس:

union Event {
    event_id: u32,
    event1: Event1,
    event2: Event2,
    event3: Event3,
}

struct Event1 {
    event_id: u32, // always EVENT1
    // ... more fields ...
}
// ... more event structs ...

match u.event_id {
    EVENT1 => { /* ... */ }
    EVENT2 => { /* ... */ }
    EVENT3 => { /* ... */ }
    _ => { /* unknown event */ }
}

هذا رمز صالح تمامًا يمكن للناس كتابته وسيكتبونه باستخدام النقابات.

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

هل ينبغي أن يعمل هذا القانون 1) للنقابات 2) لغير النقابات؟
بالنسبة لي ، الإجابة واضحة بـ "لا" في كلتا الحالتين ، لأن هذه فئة كاملة من الأخطاء التي يمكن لراست أن يمنعها ويريد منعها ، بغض النظر عن "اتحاد" T.

جيد بالنسبة لي.

أبسط مخطط للنقابات سيكون "نفس القواعد المعمول بها في البنيات + (de) تهيئة / استعارة حقل كما (de) يهيئ / يستعير حقول أشقائه".

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

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

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

لا يمكنني المبالغة في تقدير مدى أهمية وجود قواعد قابلة للفحص بشكل ديناميكي. أعتقد أننا يجب أن نهدف إلى الحصول على 0 حالات لا يمكن التحقق منها من UB. (لم نصل إلى هناك بعد ، ولكن هذا هو الهدف الذي يجب أن نحققه). هذه هي الطريقة المسؤولة الوحيدة للحصول على UB بلغتك ، كل شيء آخر هو حالة مؤلفي المترجمين / اللغويين الذين يجعلون حياتهم أسهل على حساب كل من يجب أن يتعايش مع العواقب. (أنا أعمل حاليًا على قواعد قابلة للتحقيق ديناميكيًا للتعرجات ووصول المؤشر الأولي.)
حتى لو كانت هذه هي المشكلة الوحيدة ، بقدر ما أشعر بالقلق "لا يمكن التحقق ديناميكيًا" ، فهي أسباب كافية لعدم استخدام هذا النهج.

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

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

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

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

ضع في اعتبارك اتحادًا موسومًا بنمط C يكون قابلاً للتوسيع بوضوح مع المزيد من العلامات في المستقبل. يجب ألا تفترض قراءة كود C و Rust لهذا الاتحاد أنه يعرف كل نوع حقل ممكن.

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

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

هذا ديناميكي لا يمكن التحقق منه (على الأقل بالنسبة للنقابات التي تحتوي على> 1 حقل وخارج نطاق المقيم)

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

توضيح: قصدت عدم التحقق في "افتراضيًا" / "في وضع الإصدار" ، بالطبع يمكن التحقق منه في "الوضع البطيء" مع بعض الأدوات الإضافية ، لكنك كتبت بالفعل عن هذا أفضل مما أستطيع.

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

النموذج الذي اقترحه petrochenkov يسمح

نعم ، فهمت أن هذا كان الاقتراح.

ومع ذلك ، لا أعتقد أن ذلك يجب أن يكون ضروريًا. يمكن تصور ، مولدات الربط يمكن أن تضيف مثل هذا المجال.

يمكنهم ذلك ، لكن عليهم إضافته بشكل منهجي إلى كل اتحاد.

لا يزال يتعين عليّ أن أرى حجة حول سبب وجوب كسر حالات الاستخدام الأساسي للنقابات لصالح بعض حالات الاستخدام غير المحددة التي تعتمد على الحد من أنماط البت التي يمكن أن تحتويها.

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

حالات الاستخدام الأساسي للنقابات

ليس من الواضح بالنسبة لي سبب كون هذه حالة الاستخدام الأساسية.
قد يكون هذا صحيحًا بالنسبة للنقابات repr(C) إذا افترضت أن جميع استخدامات النقابات للنقابات الموسومة / "مضاهاة Rust enum" في FFI تفترض قابلية التمدد (وهذا ليس صحيحًا) ، ولكن مما رأيته ، استخدامات لا تتوقع النقابات repr(Rust) (التحكم بالإسقاط ، التحكم في التهيئة ، أجهزة الإرسال) ظهور "متغيرات غير متوقعة" فجأة فيها.

petrochenkov أنا لا أقول "كسر حالة استخدام الأولية"، قلت: "كسر حالات الاستخدام الأساسي". FFI هي إحدى حالات الاستخدام الأساسية للنقابات.

وأخذ الاتحاد (هيه ؛)) من كل تلك المجموعات

من المؤكد أن هناك بديهية جذابة لبيان أن "القيم المحتملة للاتحاد هي اتحاد القيم الممكنة لجميع متغيراته المحتملة" ...

صحيح. ومع ذلك ، هذا ليس الاقتراح - نتفق جميعًا على أن ما يلي يجب أن يكون قانونيًا:

union F {
  x: (u8, bool),
  y: (bool, u8),
}
fn foo() -> F {
  let mut f = F { x: (5, false) };
  unsafe { f.y.1 = 17; }
  f
}

في الواقع أعتقد أنه خطأ أن هذا يتطلب unsafe .

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

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

في الواقع أعتقد أنه من الخطأ أن هذا يتطلب حتى غير آمن.

لا أعلم شيئًا عن تطبيق مدقق عدم الأمان الجديد المستند إلى MIR ، ولكن في التطبيق القديم المستند إلى HIR ، كان بالتأكيد تحديدًا / تبسيطًا للمدقق - فقط تعبيرات النموذج expr1.field = expr2 تم تحليلها لاحتمال "حقل" التنازل عن "عدم الأمان" ، تم التعامل مع كل شيء آخر بحذر على أنه "وصول ميداني" عام وغير آمن للنقابات

الرد على التعليق في https://github.com/rust-lang/rust/issues/52786#issuecomment -408645420:

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

لست متأكدًا من مدى ارتباط جزء عقد Wrap s حول عدم وجود قيم غير متوقعة بخصوصية الحقل.

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

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

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

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

تضمين التغريدة
هل هذا يصف موقفك بدقة؟

كيف يتم التعامل مع مثل هذه السيناريوهات؟

mod m {
    union MyPrivateUnion { /* private fields */ }
    extern {
        fn my_private_ffi_function() -> MyPrivateUnion; // Can return garbage (?)
    }
}

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

لا ، ليس هذا ما قصدته.

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

  • يتم تحديد "ثابت على مستوى التخطيط" (أو "ثابت نحوي") من النوع تمامًا من خلال الشكل النحوي للنوع. هذه أشياء مثل " &mut T غير فارغ ومحاذاة" ، " bool هو 0 أو 1 " ، " ! لا يمكن يوجد". على هذا المستوى ، *mut T هو نفسه usize - كلاهما يسمح بأي قيمة (أو ربما أي قيمة مهيأة ، لكن هذا التمييز مخصص لمناقشة أخرى). في النهاية ، سيكون لدينا مستند يشرح هذه الثوابت لجميع الأنواع ، من خلال التكرار الهيكلي: الثابت على مستوى التخطيط للبنية هو أن جميع حقولها يتم الحفاظ عليها بشكل ثابت ، وما إلى ذلك. الرؤية لا تلعب دورًا هنا.
Violating the layout-level invariant is instantaneous UB. This is a statement we can make because we have defined this invariant in very simple terms, and we make it part of the definition of the language itself. We can then exploit this UB (and we already do), e.g. to perform enum layout optimizations.
  • يتم اختيار "ثابت على مستوى النوع المخصص" (أو "ثابت دلالي") من النوع من قبل أي شخص يقوم بتنفيذ النوع. لا يستطيع المترجم معرفة هذا الثابت لأننا لا نملك لغة للتعبير عنه ، وينطبق الشيء نفسه على تعريف اللغة. لا يمكننا أن ننتهك هذا UB الثابت ، حيث لا يمكننا حتى أن نقول ما هو هذا الثابت! حقيقة أنه من الممكن حتى أن يكون لديك ثوابت مخصصة هي ميزة لأي نظام كتابة مفيد: التجريد. لقد كتبت المزيد عن هذا في منشور مدونة سابق .

    العلاقة بين العرف ، الدلالي الثابت و UB هو أننا نعلن أن الشفرة غير الآمنة قد تعتمد على ثوابتها الدلالية التي يتم الحفاظ عليها بواسطة كود خارجي . هذا يجعل من الخطأ المضي قدمًا في وضع أي أشياء عشوائية في حقل بحجم Vec . لاحظ أنني قلت خطأ (أستخدم أحيانًا مصطلح غير سليم ) - لكن ليس سلوكًا غير محدد! مثال آخر لإثبات هذا الاختلاف (في الواقع ، نفس المثال) هو المناقشة حول قواعد التسمية المستعارة لـ &mut ZST . إن إنشاء رمز متدلي غير فارغ ومحاذاة جيداً &mut ZST لا يُعد UB فوريًا ، لكنه لا يزال غير صحيح / غير سليم لأن المرء قد يكتب رمزًا غير آمن يعتمد على عدم حدوث ذلك.

سيكون من الجيد التوفيق بين هذين المفهومين ، لكنني لا أعتقد أنه عملي. بادئ ذي بدء ، بالنسبة لبعض الأنواع (مؤشرات الوظيفة ، سمات dyn) ، فإن تعريف الثابت الدلالي المخصص يستخدم في الواقع تعريف UB في اللغة. سيكون هذا التعريف دائريًا إذا أردنا أن نقول إنه من UB أن ينتهك العرف الدلالي الثابت. ثانيًا ، أفضل ما إذا كان تعريف لغتنا ، وما إذا كان تتبع تنفيذ معين يعرض UB ، خاصية قابلة للتقرير. غالبًا ما تكون الثوابت الدلالية والمخصصة غير قابلة للتقرير.


لست متأكدًا من كيفية ارتباط جزء عقد Wraps بالضبط بشأن عدم وجود قيم غير متوقعة بخصوصية المجال.

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

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

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

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


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

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

let sz = self.size;
self.size = 1337;
self.size = sz;

ولا يوجد UB.


mod m {
    union MyPrivateUnion { /* private fields */ }
    extern {
        fn my_private_ffi_function() -> MyPrivateUnion; // Can return garbage (?)
    }
}

من حيث ثابت التخطيط النحوي ، يمكن لـ my_private_ffi_function فعل أي شيء (بافتراض أن دالة استدعاء ABI ومطابقات التوقيع). فيما يتعلق بالثابت المخصص الدلالي ، فهذا غير مرئي في الكود - أيا كان من كتب هذه الوحدة كان لديه شيء ثابت في الاعتبار ، يجب عليهم توثيقه بجانب تعريف الاتحاد الخاص بهم ، ثم التأكد من أن دالة FFI تُرجع قيمة ترضي الثابت .

لقد كتبت أخيرًا &mut T ومتى يجب تهيئة ، ونوعين من الثوابت التي ذكرتها أعلاه.

هل هناك أي شيء متبقي لتتبعه هنا لم يتم تغطيته بالفعل بواسطة https://github.com/rust-lang/rust/issues/55149 ، أم يجب علينا إغلاقه؟

لا يزال E0658 يشير هنا:

خطأ [E0658]: النقابات ذات الحقول غير Copy غير مستقرة (راجع المشكلة رقم 32836)

هذا يلعب حاليًا بشكل رهيب مع atomics ، نظرًا لأنها لا تنفذ Copy . لا أحد يعرف الحل؟

عند تنفيذ https://github.com/rust-lang/rust/issues/55149 ، ستتمكن من استخدام ManuallyDrop<AtomicFoo> في الاتحاد. حتى ذلك الحين ، الحل الوحيد هو استخدام Nightly (أو عدم استخدام union والعثور على بديل).

مع تنفيذ ذلك ، لن تحتاج حتى إلى ManuallyDrop ؛ بعد كل شيء يعرف rustc أن Atomic* لا يطبق Drop .

تكليف نفسي بتبديل مشكلة التعقب إلى المشكلة الجديدة.

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