Rust: مشكلة في تتبع التخصص (RFC 1210)

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

هذه مشكلة تتبع للتخصص (rust-lang / rfcs # 1210).

خطوات التنفيذ الرئيسية:

  • [x] أرض https://github.com/rust-lang/rust/pull/30652 =)
  • [] قيود حول الإرسال مدى الحياة (حاليًا ثقب سلامة )
  • [] default impl (https://github.com/rust-lang/rust/issues/37653)
  • [] التكامل مع الثوابت المرتبطة
  • [] لا يتم دائمًا تنفيذ الحدود بشكل صحيح (https://github.com/rust-lang/rust/issues/33017)
  • [] هل يجب أن نسمح بالتضمينات الفارغة إذا لم يكن لدى الوالدين أعضاء في default ؟ https://github.com/rust-lang/rust/issues/48444
  • [] تنفيذ عبارة "قابلة للتطبيق دائمًا" تشير إلى https://github.com/rust-lang/rust/issues/48538
  • [] وصف واختبر ظروف الدورة الدقيقة حول إنشاء الرسم البياني للتخصص (انظر على سبيل المثال هذا التعليق ، الذي أشار إلى أن لدينا بعض المنطق الدقيق للغاية هنا اليوم)

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

  • هل يجب أن يكون النوع المرتبط قابلاً للتخصص على الإطلاق؟
  • متى يجب أن يكشف الإسقاط عن default type ؟ أبدا أثناء الكتابة؟ أو عندما أحادي الشكل؟
  • هل يجب اعتبار عناصر السمات الافتراضية default (أي قابلة للتخصيص)؟
  • هل يجب أن يكون لدينا default impl (حيث تكون جميع العناصر default ) أو partial impl (حيث يكون default مشتركًا) ؛ راجع https://github.com/rust-lang/rust/issues/37653#issuecomment -616116577 للحصول على بعض الأمثلة ذات الصلة حيث يتم تقييد default impl .
  • كيف يجب أن نتعامل مع قابلية الانتشار مدى الحياة؟

لاحظ أن ميزة specialization كما تم تطبيقها حاليًا غير سليمة ، مما يعني أنها يمكن أن تسبب سلوكًا غير محدد بدون رمز unsafe . min_specialization يتجنب معظم المزالق .

A-specialization A-traits B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-specialization T-lang

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

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

#![feature(rustc_attrs, min_specialization)]

ثم إذا كنت بحاجة إلى إجراء سمة مسند التخصص كما هو موضح في الحد الأدنى من التخصص ، فقم بتمييز إعلان السمة بـ #[rustc_specialization_trait] .

تم الانتهاء من كل تخصصي في هذا الملف ، وهنا مثال على سمة مسند التخصص.

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

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

https://github.com/LLFourn/secp256kfun/blob/6766b60c02c99ca24f816801fe876fed79643c3a/secp256kfun/src/op.rs#L196 -L206

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

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

impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
{
    // no need for default
    fn extend(&mut self, iterable: T) {
        ...
    }
}

// We declare explicitly which impl we are specializing repeating all type bounds etc
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
    // And then we declare explicitly how we are making this impl narrower with ‘when’.
    // i.e. This impl is like the first except replace all occurances of ‘T’ with ‘&'a [A]’
    when<'a> T = &'a [A]
{
    fn extend(&mut self, iterable: &'a [A]) {
        ...
    }
}

ال 236 كومينتر

بعض الأسئلة المفتوحة الإضافية:

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

لست متأكدا من أن التخصص يغير قواعد اليتيم:

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

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

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

هل ينبغي السماح بذلك مع التخصص كما هو مطبق؟ أم هل فاتني شيء؟
http://is.gd/3Ul0pe

نفس الشيء مع هذا ، كان من المتوقع أن يتم تجميعه: http://is.gd/RyFIEl

يبدو أن هناك بعض المراوغات في تحديد التداخل عند تضمين الأنواع المرتبطة. هذا بتجميع: http://is.gd/JBPzIX ، في حين أن هذا الرمز متطابقة بشكل فعال لا: http://is.gd/0ksLPX

هذا جزء من الكود الذي كنت أتوقع تجميعه مع التخصص:

http://is.gd/3BNbfK

#![feature(specialization)]

use std::str::FromStr;

struct Error;

trait Simple<'a> {
    fn do_something(s: &'a str) -> Result<Self, Error>;
}

impl<'a> Simple<'a> for &'a str {
     fn do_something(s: &'a str) -> Result<Self, Error> {
        Ok(s)
    }
}

impl<'a, T: FromStr> Simple<'a> for T {
    fn do_something(s: &'a str) -> Result<Self, Error> {
        T::from_str(s).map_err(|_| Error)
    }
}

fn main() {
    // Do nothing. Just type check.
}

فشل التجميع مع المترجم الذي يشير إلى تعارضات في التنفيذ. لاحظ أن &str لا يطبق FromStr ، لذلك يجب ألا يكون هناك تعارض.

sgrif

كان لدي الوقت للنظر في أول مثالين. ها هي ملاحظاتي.

مثال 1

الحالة الأولى ، لديك:

  • FromSqlRow<ST, DB> for T where T: FromSql<ST, DB>
  • FromSqlRow<(ST, SU), DB> for (T, U) where T: FromSqlRow<ST, DB>, U: FromSqlRow<SU, DB>,

تكمن المشكلة في أن هذه الضمانات تتداخل ولكن ليس أي منها أكثر تحديدًا من الآخر:

  • من المحتمل أن يكون لديك T: FromSql<ST, DB> حيث لا يكون T زوجًا (لذا فهو يتطابق مع الضم الأول وليس الثاني).
  • من المحتمل أن يكون لديك (T, U) حيث:

    • T: FromSqlRow<ST, DB> ،

    • U: FromSqlRow<SU, DB> ، لكن _not_

    • (T, U): FromSql<(ST, SU), DB>

    • (لذا فإن الضمينة الثانية تتطابق ، لكن ليست الأولى)

  • يتداخل الضمنيان لأنه يمكن أن يكون لديك (T, U) مثل:

    • T: FromSqlRow<ST, DB>

    • U: FromSqlRow<SU, DB>

    • (T, U): FromSql<(ST, SU), DB>

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

مثال 2

عندك:

  • Queryable<ST, DB> for T where T: FromSqlRow<ST, DB>
  • Queryable<Nullable<ST>, DB> for Option<T> where T: Queryable<ST, DB>

تتداخل هذه العوامل لأنه يمكن أن يكون لديك Option<T> حيث:

  • T: Queryable<ST, DB>
  • Option<T>: FromSqlRow<Nullable<ST>, DB>

لكن لا يوجد ضمني أكثر تحديدًا:

  • يمكن أن يكون لديك T بحيث أن T: FromSqlRow<ST, DB> لكن T ليس Option<U> (يتطابق مع الضم الأول وليس الثاني)
  • يمكن أن يكون لديك Option<T> مثل T: Queryable<ST, DB> لكن ليس Option<T>: FromSqlRow<Nullable<ST>, DB>

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

فشل التجميع مع المترجم الذي يشير إلى تعارضات في التنفيذ. لاحظ أن &str لا يطبق FromStr ، لذلك يجب ألا يكون هناك تعارض.

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

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

شكرا لتوضيح هاتين الحالتين. من المنطقي تماما الآن

في الثلاثاء ، 22 مارس 2016 ، 6:34 مساءً كتب آرون تورون [email protected] :

SergioBenitez https://github.com/SergioBenitez

فشل التجميع مع المترجم الذي يشير إلى تعارضات في التنفيذ. ملحوظة
هذا & str لا يطبق FromStr ، لذلك لا ينبغي أن يكون هناك تعارض.

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

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

-
http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/

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

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

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

أليس هذا بالضبط ما يحاول التخصص معالجته؟ مع التخصص ، أتوقع أنه حتى إذا تمت إضافة تطبيق FromStr لـ &str في المستقبل ، فإن التنفيذ المباشر للسمة Simple لـ &str الأسبقية.

SergioBenitez تحتاج إلى وضع default fn في التضمين الأكثر عمومية. الخاص بك
المثال غير متخصص.

في الثلاثاء ، 22 مارس 2016 ، الساعة 6:54 مساءً ، سيرجيو بينيتيز ، إخطارات github.com
كتب:

aturon https://github.com/aturon

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

أليس هذا بالضبط ما يحاول التخصص معالجته؟ مع
التخصص ، أتوقع أنه حتى لو تم تنفيذ FromStr
تم إضافة for & str في المستقبل ، التنفيذ المباشر لـ
ستأخذ سمة & str الأسبقية.

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

أعتقد أن عناصر السمات "الافتراضية" التي يتم اعتبارها تلقائيًا default تبدو مربكة. قد ترغب في كلٍّ من المعلمتين لسمة كما في Haskell ، وما إلى ذلك جنبًا إلى جنب مع تخفيف impl s. كما لا يمكنك بسهولة grep بالنسبة لهم كما يمكنك مقابل default . ليس من الصعب كتابة الكلمة الرئيسية default وتقديم تطبيق افتراضي ، ولكن لا يمكن فصلهما كما هو. أيضًا ، إذا أراد المرء توضيح اللغة ، فيمكن إعادة تسمية عناصر السمات "الافتراضية" هذه إلى عناصر "سمة مقترحة" في التوثيق.

ملاحظة من رقم 32999 (تعليق) : إذا

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

لماذا لا تعمل؟ الحيلة تحدد التخصص بسمة خاصة. لا يمكنك تخصيص السمة الخاصة إذا لم تتمكن من الوصول إليها.

@ arielb1 آه. نقطة جيدة. في حالتي ، السمة ليست خاصة.

لا أعتقد أن الاستدلال "الخارجي لا يمكن أن يتخصص لأن التوافق مع المستقبل + قواعد الاتساق اليتيمة" مثير للاهتمام أو مفيد بشكل خاص. خاصة عندما لا نلتزم بقواعد الاتساق المحددة الخاصة بنا.

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

سيسمح السماح بإسقاط الأنواع الافتراضية المرتبطة أثناء فحص النوع بفرض عدم المساواة في النوع في وقت الترجمة: https://gist.github.com/7c081574958d22f89d434a97b626b1e4

#![feature(specialization)]

pub trait NotSame {}

pub struct True;
pub struct False;

pub trait Sameness {
    type Same;
}

mod internal {
    pub trait PrivSameness {
        type Same;
    }
}

use internal::PrivSameness;

impl<A, B> Sameness for (A, B) {
    type Same = <Self as PrivSameness>::Same;
}

impl<A, B> PrivSameness for (A, B) {
    default type Same = False;
}
impl<A> PrivSameness for (A, A) {
    type Same = True;
}

impl<A, B> NotSame for (A, B) where (A, B): Sameness<Same=False> {}

fn not_same<A, B>() where (A, B): NotSame {}

fn main() {
    // would compile
    not_same::<i32, f32>();

    // would not compile
    // not_same::<i32, i32>();
}

تم تحريره حسب تعليق burdges

فقط fyirphmeier يجب على المرء تجنب is.gd لأنه لم يتم حله لمستخدمي Tor بسبب استخدام CloudFlare. يعمل GitHub بشكل جيد مع عناوين URL الكاملة. ويعمل play.rust-lang.org بشكل جيد على Tor.

burdges FWIW يستخدم play.rust-lang.org نفسه is.gd للزر "Shorten" الخاص به.

ربما يمكن تغييره ، على الرغم من: https://github.com/rust-lang/rust-playpen/blob/9777ef59b/static/web.js#L333

استخدم مثل هذا (https://is.gd/Ux6FNs):

#![feature(specialization)]
pub trait Foo {}
pub trait Bar: Foo {}
pub trait Baz: Foo {}

pub trait Trait {
    type Item;
}

struct Staff<T> { }

impl<T: Foo> Trait for Staff<T> {
    default type Item = i32;
}

impl<T: Foo + Bar> Trait for Staff<T> {
    type Item = i64;
}

impl<T: Foo + Baz> Trait for Staff<T> {
    type Item = f64;
}

fn main() {
    let _ = Staff { };
}

خطأ:

error: conflicting implementations of trait `Trait` for type `Staff<_>`: [--explain E0119]
  --> <anon>:20:1
20 |> impl<T: Foo + Baz> Trait for Staff<T> {
   |> ^
note: conflicting implementation is here:
  --> <anon>:16:1
16 |> impl<T: Foo + Bar> Trait for Staff<T> {
   |> ^

error: aborting due to previous error

هل يدعم feture specialization هذا ، وهل هناك أي نوع آخر من التطبيقات حاليًا؟

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

لا يسمح تصميم التخصص الحالي بهذه التلميحات ، لأنه لا T: Foo + Bar ولا T: Foo + Baz أكثر تخصصًا من الآخر. بمعنى ، إذا كان لديك بعض T: Foo + Bar + Baz ، فليس من الواضح ما الذي يجب أن "يفوز".

لدينا بعض الأفكار حول نظام أكثر تعبيراً من شأنه أن يتيح لك _أيضاً_ تقديم ضمانة T: Foo + Bar + Baz وبالتالي إزالة الغموض ، لكن لم يتم اقتراح ذلك بالكامل بعد.

إذا كانت السمة السالبة trait Baz: !Bar الإطلاق ، فيمكن استخدام ذلك أيضًا مع التخصص لإثبات أن مجموعات الأنواع التي تنفذ Bar وتلك التي تطبق Baz متميزة وقابلة للتخصص بشكل فردي.

يبدو أن ردrphmeier هو ما أريده تمامًا ، كما أن الإشارة إلى T: Foo + Bar + Baz ستساعد أيضًا.

فقط تجاهل هذا ، لا يزال لديّ شيء أفعله مع حالتي ، ودائمًا ما يكون مثيرًا للهبوط specialization والميزات الأخرى.

بفضلaturonrphmeier.

لقد كنت ألعب مع التخصص مؤخرًا ، وقد صادفت هذه الحالة الغريبة:

#![feature(specialization)]

trait Marker {
    type Mark;
}

trait Foo { fn foo(&self); }

struct Fizz;

impl Marker for Fizz {
    type Mark = ();
}

impl Foo for Fizz {
    fn foo(&self) { println!("Fizz!"); }
}

impl<T> Foo for T
    where T: Marker, T::Mark: Foo
{
    default fn foo(&self) { println!("Has Foo marker!"); }
}

struct Buzz;

impl Marker for Buzz {
    type Mark = Fizz;
}

fn main() {
    Fizz.foo();
    Buzz.foo();
}

إخراج المترجم:

error: conflicting implementations of trait `Foo` for type `Fizz`: [--explain E0119]
  --> <anon>:19:1
19 |> impl<T> Foo for T
   |> ^
note: conflicting implementation is here:
  --> <anon>:15:1
15 |> impl Foo for Fizz {
   |> ^

روضة

أعتقد أن ما ورد أعلاه _يجب_ تجميعه ، وهناك نوعان مختلفان مثيران للاهتمام يعملان بالفعل على النحو المنشود:

1) إزالة الحد where T::Mark: Fizz المربوط:

impl<T> Foo for T
    where T: Marker //, T::Mark: Fizz
{
    // ...
}

روضة

2) إضافة "اسم مستعار مرتبط بالسمة":

trait FooMarker { }
impl<T> FooMarker for T where T: Marker, T::Mark: Foo { }

impl<T> Foo for T where T: FooMarker {
    // ...
}

روضة

(الذي _ لا يعمل_ إذا تم تعريف Marker في صندوق منفصل (!) ، انظر هذا المثال الريبو )

أعتقد أيضًا أن هذه المشكلة قد تكون مرتبطة بالرقم 20400 بطريقة ما

تحرير : لقد فتحت مشكلة حول هذا: # 36587

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

use std::vec::IntoIter as VecIntoIter;

pub trait ClonableIterator: Iterator {
    type ClonableIter;

    fn clonable(self) -> Self::ClonableIter;
}

impl<T> ClonableIterator for T where T: Iterator {
    default type ClonableIter = VecIntoIter<T::Item>;

    default fn clonable(self) -> VecIntoIter<T::Item> {
        self.collect::<Vec<_>>().into_iter()
    }
}

impl<T> ClonableIterator for T where T: Iterator + Clone {
    type ClonableIter = T;

    #[inline]
    fn clonable(self) -> T {
        self
    }
}

( روضة )
(بالمناسبة ، سيكون من الجيد إذا وصل هذا الرمز في النهاية إلى stdlib يومًا ما)

فشل هذا الرمز مع:

error: method `clonable` has an incompatible type for trait:
 expected associated type,
    found struct `std::vec::IntoIter` [--explain E0053]
  --> <anon>:14:5
   |>
14 |>     default fn clonable(self) -> VecIntoIter<T::Item> {
   |>     ^

يؤدي تغيير قيمة الإرجاع إلى Self::ClonableIter ظهور الخطأ التالي:

error: mismatched types [--explain E0308]
  --> <anon>:15:9
   |>
15 |>         self.collect::<Vec<_>>().into_iter()
   |>         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::vec::IntoIter`
note: expected type `<T as ClonableIterator>::ClonableIter`
note:    found type `std::vec::IntoIter<<T as std::iter::Iterator>::Item>`

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

tomaka يجب أن يعمل ، نص RFC يحتوي على هذا:

impl<T> Example for T {
    default type Output = Box<T>;
    default fn generate(self) -> Box<T> { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> bool { self }
}

(https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#the-default-keyword)

الذي يبدو مشابهًا بدرجة كافية لقضيتك ليكون ذا صلة.

satch هذا المثال لا يبدو أنه يجمع مع التعريف البديهي https://play.rust-lang.org/؟gist=97ff3c2f7f3e50bd3aef000dbfa2ca4e&version=nightly&backtrace=0

لا يسمح رمز التخصص صراحة بهذا - راجع # 33481 ، الذي اعتقدت في البداية أنه خطأ ولكن تبين أنه مشكلة تشخيصية. لقد مرت تقارير العلاقات العامة الخاصة بي لتحسين التشخيصات هنا دون أن يلاحظها أحد ، ولم أحافظ عليها لأحدث معلم منذ بعض الوقت.

rphmeier يقترح نص RFC أنه يجب السماح به على الرغم من نسخ هذا المثال منه.

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

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

هل يجب السماح بما يلي؟ أريد أن تخصص نوعًا ما بحيث يكون ArrayVec هو Copy عندما يكون نوع العنصر الخاص به هو Copy ، وأن يكون له مدمر. أحاول تحقيقه باستخدام مجال داخلي يحل محله التخصص.

كنت آمل أن يتم تجميع هذا ، أي أنه يستنتج إمكانية نسخ حقول ArrayVec<A> من أنواع الحقول التي تم تحديدها بواسطة A: Copy + Array bound (مقتطف قابل للترجمة في الملعب) .

impl<A: Copy + Array> Copy for ArrayVec<A>
    //where <A as Repr>::Data: Copy
{ }

بند التعليق الذي تم التعليق عليه غير مطلوب لأنه يعرض نوعًا خاصًا Repr في الواجهة العامة. (إنه أيضًا ICEs على أي حال).

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

متابعة تعليقي ، حالة الاستخدام الفعلي:

// Ideal version

trait Scannable {}

impl<T: FromStr> Scannable for T {}
impl<T: FromStr> Scannable for Result<T, ()> {}

// But this doesn't follow from the specialisation rules because Result: !FromStr
// Lattice rule would allow filling in that gap or negative reasoning would allow specifying it.

// Second attempt

trait FromResult {
    type Ok;
    fn from(r: Result<Self::Ok, ()>) -> Self;
}

impl<T> Scannable for T {
    default type Ok = T;
    default fn from(r: Result<T, ()>) -> Self {...} // error can't assume Ok == T, could do this if we had `default impl`
}

impl<T> Scannable for Result<T, ()> {
    type Ok = T;
    default fn from(r: Result<T, ()>) -> Self { r }
}

fn scan_from_str<T: FromResult>(x: &str) -> T
    where <T as FromResult>::Ok: FromStr  // Doesn't hold for T: FromStr because of the default on T::Ok
{ ... }

// Can also add the FromStr bound to FromResult::Ok, but doesn't help

// Third attempt
trait FromResult<Ok> {
    fn from(r: Result<Ok, ()>) -> Self;
}

impl<T> FromResult<T> for T {
    default fn from(r: Result<Self, ()>) -> Self { ... }
}

impl<T> FromResult<T> for Result<T, ()> {
    fn from(r: Result<T, ())>) -> Self { r }
}


fn scan_from_str<U: FromStr, T: FromResult<U>>(x: &str) -> T { ... }

// Error because we can't infer that U == String
let mut x: Result<String, ()> = scan_from_str("dsfsf");

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

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

impl<T> ClonableIterator for T where T: Iterator {
    default type ClonableIter = VecIntoIter<T::Item>;

    default fn clonable(self) -> VecIntoIter<T::Item> {
    //                           ^^^^^^^^^^^^^^^^^^^^
        self.collect::<Vec<_>>().into_iter()
    }
}

في المكان الذي أبرزته ، يعتمد clonable على Self::ClonableIter ، ولكن نظرًا لأنه تم الإعلان عن CloneableIter كإعداد افتراضي ، لا يمكنك فعل ذلك. القلق هو أن شخصًا ما قد يتخصص ويتجاوز CloneableIter لكن _not_ clonable .

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

impl<T> ClonableIterator for T where T: Iterator {
    default {
        type ClonableIter = VecIntoIter<T::Item>;
        fn clonable(self) -> VecIntoIter<T::Item> { ... }
    }
}

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

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

pub trait ClonableIterator: Iterator {
    fn clonable(self) -> impl Iterator;
}

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

ملاحظة ، آسف للتأخير الطويل في الرد على رسائلك ، والتي أراها من تاريخ _ يوليو.

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

أنا مهتم بتنفيذ ميزة default impl (حيث تكون جميع العناصر default ).
هل تقبل المساهمة في ذلك؟

تضمين التغريدة سأكون سعيدًا للمساعدة في توجيه العمل أيضًا.

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

فيما يلي تبسيط لحالة الاستخدام الخاصة بي ، مما يدل على الحاجة إلى أنواع مرتبطة قابلة للتخصص.
لدي بنية بيانات عامة ، على سبيل المثال Foo ، والتي تنسق مجموعة من كائنات سمة الحاوية ( &trait::Property ). يتم تنفيذ السمة trait::Property بواسطة كل من Property<T> (مدعوم بـ Vec<T> ) و PropertyBits (مدعوم بـ BitVec ، متجه قليلاً).
في الأساليب العامة على Foo ، أود أن أكون قادرًا على تحديد بنية البيانات الأساسية الصحيحة لـ T عبر الأنواع المرتبطة ، لكن هذا يتطلب التخصص للحصول على ضمانة شاملة للحالات غير الخاصة مثل يتبع.

trait ContainerFor {
    type P: trait::Property;
}

impl<T> ContainerFor for T {
    default type P = Property<T>; // default to the `Vec`-based version
}

impl ContainerFor for bool {
    type P = PropertyBits; // specialize to optimize for space
}

impl Foo {
    fn add<T>(&mut self, name: &str) {
        self.add_trait_obj(name, Box::new(<T as ContainerFor>::P::new())));
    }
    fn get<T>(&mut self, name: &str) -> Option<&<T as ContainerFor>::P> {
        self.get_trait_obj(name).and_then(|prop| prop.downcast::<_>());
    }
}

شكرا aturon !
في الأساس ، أقوم بهذا العمل عن طريق إضافة سمة "تقصير" جديدة إلى هيكل ast::ItemKind::Impl (ثم ​​استخدم السمة الجديدة جنبًا إلى جنب مع سمة "defaultness" العنصر الضمني) ولكن هناك أيضًا خاصية سريعة وسهلة
الاحتمال الذي يتكون من تعيين افتراضي لجميع العناصر الضمنية لـ default impl أثناء التحليل.
بالنسبة لي ، هذا ليس حلاً "كاملاً" لأننا فقدنا المعلومات التي تفيد بأن "التقصير" مرتبط بالضمانات وليس بكل عنصر ضمني ،
بالإضافة إلى ذلك ، إذا كانت هناك خطة لتقديم partial impl فإن الحل الأول سيوفر بالفعل سمة يمكن استخدامها لتخزين default بالإضافة إلى partial . ولكن فقط للتأكد و
لا تضيع الوقت ، ما رأيك؟

giannicicaturon هل يمكنني اقتراح إنشاء مشكلة معينة لمناقشة default impl ؟

لا تهتم ، لقد قمت بإنشاء واحد: https://github.com/rust-lang/rust/issues/37653

هل تسمح لي قاعدة الشبكة ، بالنظر إلى:

trait Foo {}

trait A {}
trait B {}
trait C {}
// ...

أضف تطبيقات Foo لمجموعة فرعية من الأنواع التي تنفذ مجموعة من A ، B ، C ، ...:

impl Foo for T where T: A { ... }
impl Foo for T where T: B { ... }
impl Foo for T where T: A + B { ... }
impl Foo for T where T: B + C { ... }
// ...

واسمحوا لي "بمنع" بعض التركيبات ، على سبيل المثال ، أن A + C يجب ألا يحدث أبدًا:

impl Foo for T where T: A + C = delete;

؟

السياق: لقد رغبت في ذلك عند تطبيق سمة ApproxEqual(Shape, Shape) لأنواع مختلفة من الأشكال (النقاط ، المكعبات ، المضلعات ، ...) حيث تكون هذه كلها سمات. اضطررت إلى التغلب على هذا من خلال إعادة هيكلة هذا إلى سمات مختلفة ، على سبيل المثال ، ApproxEqualPoint(Point, Point) ، لتجنب عمليات التنفيذ المتضاربة.

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

واسمحوا لي "بمنع" بعض التركيبات ، على سبيل المثال ، أن A + C يجب ألا يحدث أبدًا:

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

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

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

@ nikomatsakis :

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

فكرت في مجموعات الاستثناءات أثناء كتابة هذا (لقد ذكرتها في المنتديات في اليوم الآخر) ، لكنني لا أعتقد أنها يمكن أن تعمل لأنه في هذا المثال بالذات ، ليست كل تطبيقات السمات حصرية. المثال الأكثر تافهًا هو السمات Point و Float : يمكن أن تكون Float _ نقطة 1D ، لذلك لا يمكن أن تكون ApproxEqualPoint(Point, Point) و ApproxEqualFloat(Float, Float) حصرية. هناك أمثلة أخرى مثل Square و Polygon أو Box | Cube و AABB (المربع المحيط بمحاذاة المحور) حيث يحتاج "التسلسل الهرمي للسمات" في الواقع إلى قيود أكثر تعقيدًا.

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

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

سيسمح لك التخصص الشبكي gnzlbg بإثارة الذعر الضمني ، لكن فكرة القيام بذلك تجعلني: أبكي :.

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

impl<T> !Foo for T where T: A + C { }

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

مع كل هذا النقاش حول الاستدلال السلبي والتلميحات السلبية ، أشعر بأنني مضطر لإحضار فكرة هاسكل القديمة عن "سلاسل المثيلات" مرة أخرى ( ورقة ، ورقة ، أداة تعقب مشكلات GHC ، Rust pre-RFC ) ، كمصدر محتمل للإلهام إذا لم يكن هناك شيء آخر.

الفكرة الأساسية هي أنه في أي مكان يمكنك كتابة ضمنية trait ، يمكنك أيضًا كتابة أي عدد من "الجمل الأخرى إذا كانت" تحدد قيمة impl والتي يجب أن تنطبق في حالة السابقة لم يتم ذلك ، مع وجود "جملة أخرى" اختيارية تحدد ضمنيًا سلبيًا (أي ، إذا لم يتم تطبيق أي من البنود الخاصة بـ Trait ، فسيتم تطبيق !Trait )

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

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

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

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

لا أعرف ما إذا كان من الممكن حل هذه الأمور. أريد أن أمتلك:

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

تغطية جميع الحالات صعب ، لكن إذا أرغمني المترجم على تغطية جميع الحالات:

trait Foo {}
trait A {}
trait B {}

impl<T> Foo for T where T: A { ... }
impl<T> Foo for T where T: B { ... }
// impl<T> Foo for T where T: A + B { ... }  //< compiler: need to add this impl!

ويعطيني أيضًا إشارات سلبية:

impl<T> !Foo for T where T: A + B { }
impl<T> !Foo for T where T: _ { } // _ => all cases not explicitly covered yet

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

لست متأكدًا تمامًا من الآثار المترتبة على السماح بذلك.

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

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

بالنسبة إلى https://github.com/dtolnay/quote/issues/7 ، أحتاج إلى شيء مشابه لهذا المثال من RFC الذي لا يعمل بعد. سم مكعبtomakaAatchrphmeier الذي علق عن هذا في وقت سابق.

trait Example {
    type Output;
    fn generate(self) -> Self::Output;
}

impl<T> Example for T {
    default type Output = Box<T>;
    default fn generate(self) -> Box<T> { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> bool { self }
}

لقد عثرت على الحل التالي الذي يعطي طريقة للتعبير عن نفس الشيء.

#![feature(specialization)]

use std::fmt::{self, Debug};

///////////////////////////////////////////////////////////////////////////////

trait Example: Output {
    fn generate(self) -> Self::Output;
}

/// In its own trait for reasons, presumably.
trait Output {
    type Output: Debug + Valid<Self>;
}

fn main() {
    // true
    println!("{:?}", Example::generate(true));

    // box("s")
    println!("{:?}", Example::generate("s"));
}

///////////////////////////////////////////////////////////////////////////////

/// Instead of `Box<T>` just so the "{:?}" in main() clearly shows the type.
struct MyBox<T: ?Sized>(Box<T>);

impl<T: ?Sized> Debug for MyBox<T>
    where T: Debug
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "box({:?})", self.0)
    }
}

///////////////////////////////////////////////////////////////////////////////

/// Return type of the impl containing `default fn`.
type DefaultOutput<T> = MyBox<T>;

impl Output for bool {
    type Output = bool;
}

impl<T> Example for T where T: Pass {
    default fn generate(self) -> Self::Output {
        T::pass({
            // This is the impl you wish you could write
            MyBox(Box::new(self))
        })
    }
}

impl Example for bool {
    fn generate(self) -> Self::Output {
        self
    }
}

///////////////////////////////////////////////////////////////////////////////
// Magic? Soundness exploit? Who knows?

impl<T: ?Sized> Output for T where T: Debug {
    default type Output = DefaultOutput<T>;
}

trait Valid<T: ?Sized> {
    fn valid(DefaultOutput<T>) -> Self;
}

impl<T: ?Sized> Valid<T> for DefaultOutput<T> {
    fn valid(ret: DefaultOutput<T>) -> Self {
        ret
    }
}

impl<T> Valid<T> for T {
    fn valid(_: DefaultOutput<T>) -> Self {
        unreachable!()
    }
}

trait Pass: Debug {
    fn pass(DefaultOutput<Self>) -> <Self as Output>::Output;
}

impl<T: ?Sized> Pass for T where T: Debug, <T as Output>::Output: Valid<T> {
    fn pass(ret: DefaultOutput<T>) -> <T as Output>::Output {
        <T as Output>::Output::valid(ret)
    }
}

ما زلت أعمل على https://github.com/dtolnay/quote/issues/7 وأحتاج إلى نمط ماسي. هذا هو الحل الخاص بي. cczitsen الذي سأل عن هذا سابقًا و aturon و @ rphmeier الذين ردوا.

#![feature(specialization)]

/// Can't have these impls directly:
///
///  - impl<T> Trait for T
///  - impl<T> Trait for T where T: Clone
///  - impl<T> Trait for T where T: Default
///  - impl<T> Trait for T where T: Clone + Default
trait Trait {
    fn print(&self);
}

fn main() {
    struct A;
    A.print(); // "neither"

    #[derive(Clone)]
    struct B;
    B.print(); // "clone"

    #[derive(Default)]
    struct C;
    C.print(); // "default"

    #[derive(Clone, Default)]
    struct D;
    D.print(); // "clone + default"
}

trait IfClone: Clone { fn if_clone(&self); }
trait IfNotClone { fn if_not_clone(&self); }

impl<T> Trait for T {
    default fn print(&self) {
        self.if_not_clone();
    }
}

impl<T> Trait for T where T: Clone {
    fn print(&self) {
        self.if_clone();
    }
}

impl<T> IfClone for T where T: Clone {
    default fn if_clone(&self) {
        self.clone();
        println!("clone");
    }
}

impl<T> IfClone for T where T: Clone + Default {
    fn if_clone(&self) {
        self.clone();
        Self::default();
        println!("clone + default");
    }
}

impl<T> IfNotClone for T {
    default fn if_not_clone(&self) {
        println!("neither");
    }
}

impl<T> IfNotClone for T where T: Default {
    fn if_not_clone(&self) {
        Self::default();
        println!("default");
    }
}

ضرب خطأ (أو على الأقل سلوك غير متوقع من وجهة نظري) مع التخصص واكتب الاستدلال: # 38167

يجب توقع أن يكون هذان الوسيلان صالحين مع التخصص ، أليس كذلك؟ يبدو أنه لم يتم التقاطه بنجاح.

impl<T, ST, DB> ToSql<Nullable<ST>, DB> for T where
    T: ToSql<ST, DB>,
    DB: Backend + HasSqlType<ST>,
    ST: NotNull,
{
    ...
}

impl<T, ST, DB> ToSql<Nullable<ST>, DB> for Option<T> where
    T: ToSql<ST, DB>,
    DB: Backend + HasSqlType<ST>,
    ST: NotNull,
{
    ...
}

لقد قدمت https://github.com/rust-lang/rust/issues/38516 لبعض السلوك غير المتوقع الذي صادفته أثناء العمل على بناء التخصص في Serde. على غرار https://github.com/rust-lang/rust/issues/38167 ، هذه هي الحالة التي يتم فيها تجميع البرنامج بدون التضمين المتخصص وعندما يتم إضافته يوجد خطأ في النوع. ccbluss الذي كان قلقًا بشأن هذا الموقف سابقًا.

ماذا لو سمحنا بالتخصص بدون الكلمة الرئيسية default داخل صندوق واحد ، على غرار الطريقة التي نسمح بها بالتفكير السلبي داخل صندوق واحد؟

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

impl<I> Foo for I where I: Iterator<Item = u32> { ... }
impl Foo for Vec<u32> { ... }

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

اليوم هذا لا يتراكم ، وهناك تسوس وصرير الأسنان. التخصص يحل هذه المشكلة:

default impl<I> Foo for I where I: Iterator<Item = u32> { ... }
impl Foo for Vec<u32> { ... }

لكن في حل هذه المشكلة ، أضفت عقدًا عامًا إلى صندوقك: من الممكن تجاوز الضمانة التكرارية لـ Foo . ربما لا نريد إجبارك على القيام بذلك - وبالتالي ، التخصص المحلي بدون default .


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

أعتقد أن طلب default كان في الأصل إيماءة نحو توضيح التعليمات البرمجية والتوثيق الذاتي. ومع ذلك ، [..] لا يمكنني تخصيص عنصر إلا إذا سمحت لك بتخصيص عنصر ما.

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

أشعر بالقلق لأن مجرد التفكير في RFC هذا يعطيني بالفعل ذكريات PTSD من العمل في قواعد C ++ البرمجية التي تستخدم كميات فاحشة من التحميل الزائد والميراث وليس لديها أي فكرة عن أن wtf يحدث في أي سطر من الكود الذي يحتوي على طريقة استدعاء فيه. إنني أقدر حقًا الجهود التي بذلتهاaturon لتوضيح التخصص والتوثيق الذاتي.

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

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

أشعر بالقلق لأن مجرد التفكير في RFC هذا يعطيني بالفعل ذكريات من اضطراب ما بعد الصدمة ...

ولكن كيف يمنع رفض هذا التخصيص حدوث هذه الأشياء؟

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

ليس من الجيد بالضرورة أن تمنح المستخدمين القوة متى كان لديهم استخدام جيد لها. ليس إذا كان يمكّن المستخدمين أيضًا من كتابة كود محير.

ولكن كيف يمنع رفض هذا التخصيص حدوث هذه الأشياء؟

لنفترض أنك رأيت foo.bar() وتريد إلقاء نظرة على ما يفعله bar() . في الوقت الحالي ، إذا وجدت الطريقة التي تم تنفيذها على نوع مطابق ولم يتم وضع علامة عليها default فأنت تعلم أن هذا هو تعريف الطريقة الذي تبحث عنه. مع اقتراح withoutboats ، لن يكون هذا صحيحًا - بدلاً من ذلك لن تعرف أبدًا على وجه اليقين ما إذا كنت تبحث بالفعل في الكود الذي يتم تنفيذه.

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

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

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

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

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

ليس من الجيد بالضرورة أن تمنح المستخدمين القوة متى كان لديهم استخدام جيد لها. ليس إذا كان يمكّن المستخدمين أيضًا من كتابة كود محير.

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

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

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

withoutboats ، هل أنا محق في قراءة default لأنك تريد نموذجًا مقيدًا default والذي يعني "قابل للتجاوز ، ولكن فقط في هذا الصندوق" (أي ، pub(crate) لكن مقابل default )؟ ومع ذلك ، لتبسيط الأمور ، فأنت تقترح تغيير دلالات حذف default ، بدلاً من إضافة التخرج من default -ness؟

صيح. يبدو أن القيام بشيء مثل default(crate) مبالغ فيه.

بداهة ، أتخيل أنه يمكن للمرء محاكاة ذلك من خلال ما يصدره الصندوق ، أليس كذلك؟ هل هناك أي مواقف لا يمكنك فيها ببساطة تقديم سمة مساعدة خاصة باستخدام طرق default واستدعائها من impl s الأخير الخاص بك؟ هل تريد أن يستخدم المستخدم الخاص بك default s لكن لا يقدم أيًا من منتجاته؟

صيح. إن القيام بشيء مثل الافتراضي (الصندوق) يبدو وكأنه مبالغة.

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

nikomatsakis لدي نفس الدافع ، ما أقترحه هو أننا فقط نزيل الشرط الافتراضي للتخصص في نفس الصندوق ، بدلاً من إضافة المزيد من الرافعات. :-)

إذا كان من قبيل الصدفة أن يكون هذا الإعداد الافتراضي غير المُصدر هو الاستخدام الأكثر شيوعًا ، فسيكون من الأسهل تذكر ميزة #[default_export] عن طريق القياس مع #[macro_export] . قد يكون الخيار الوسيط هو السماح لميزة التصدير هذه لخطوط pub use أو pub mod .

سيكون استخدام الكلمة الأساسية pub أفضل ، لأن وحدات الماكرو 2.0 ستدعم وحدات الماكرو كعناصر عادية وستستخدم pub بدلاً من #[macro_use] . سيكون استخدام pub للإشارة إلى الرؤية عبر اللوحة بمثابة فوز كبير لاتساقها.

withoutboats بغض النظر ، أعتقد أنك سترغب أحيانًا في التخصص محليًا ولكن ليس بالضرورة فتح الأبواب للجميع

استخدام الكلمة الرئيسية العامة سيكون أفضل

إن وجود pub default fn يعني "تصدير تقصير fn علنًا" بدلاً من التأثير على رؤية الوظيفة نفسها سيكون أمرًا مربكًا للغاية للقادمين الجدد.

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

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

على الرغم من أنها بناء جملة إلى حد ما ، إلا أنني لن أعارض عمل default(foo) مثل pub(foo) - فالتناظر بين الاثنين يفوق بشكل هامشي العبث في بناء الجملة بالنسبة لي.

تحذير من ركوب الدراجة: هل فكرنا في تسميته overridable بدلاً من default ؟ إنها وصفيّة أكثر ، و overridable(foo) تقرأ أفضل من default(foo) - يقترح الأخير "هذا هو الخيار الافتراضي ضمن النطاق foo ، ولكن قد يكون هناك شيء آخر هو الافتراضي في مكان آخر "، بينما يقول السابق" هذا يمكن تجاوزه في نطاق foo "، وهذا صحيح.

أعتقد أن السؤالين الأولين هما حقًا: هل تصدير أو عدم تصدير default ness أكثر شيوعًا بشكل ملحوظ؟ ألا يجب أن default ness هو السلوك الافتراضي؟

burdges هل لديك تسميات "نعم حالة" و "بلا حالة" للخلف ، أم أنني أسيء فهم ما تقوله؟

نعم عفوا! ثابت!

لدينا impl<T> Borrow<T> for T where T: ?Sized بحيث يمكن لربط Borrow<T> التعامل مع القيم المملوكة كما لو تم استعارتها.

أفترض أنه يمكننا استخدام التخصص لتحسين المكالمات الخارجية إلى clone من Borrow<T> ، أليس كذلك؟

pub trait CloneOrTake<T> {
    fn clone_or_take(self) -> T;
}

impl<B,T> CloneOrTake<T> for B where B: Borrow<T>, T: Clone {
    #[inline]
    default fn clone_or_take(b: B) -> T { b.clone() }
}
impl<T> CloneOrTake<T> for T {
    #[inline]
    fn clone_or_take(b: T) -> T { b };
}

أعتقد أن هذا قد يجعل Borrow<T> قابلة للاستخدام في مواقف أكثر. لقد أسقطت الحد T: ?Sized لأنه من المفترض أن يحتاج المرء Sized عند إرجاع T .

قد يكون نهج آخر

pub trait ToOwnedFinal : ToOwned {
    fn to_owned_final(self) -> Self::Owned;
}

impl<B> ToOwnedFinal for B where B: ToOwned {
    #[inline]
    default fn to_owned_final(b: B) -> Self::Owned { b.to_owned() }
}
impl<T> ToOwnedFinal for T {
    #[inline]
    fn to_owned_final(b: T) -> T { b };
}

لقد حققنا بعض الاكتشافات التي قد تكون مقلقة اليوم ، يمكنك قراءة سجلات IRC هنا: https://botbot.me/mozilla/rust-lang/

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

هناك شيء واحد أشعر بالتأكد منه إلى حد ما وهو أن طلب default لا يمكن جعله متوافقًا مع ضمان أن إضافة أدوات default متوافقة دائمًا مع الإصدارات السابقة. ها هي المظاهرة:

صندوق parent v 1.0.0

trait A { }
trait B { }
trait C {
    fn foo(&self);
}

impl<T> C for T where T: B {
    // No default, not specializable!
    fn foo(&self) { panic!() }
}

صندوق client (يعتمد على parent )

extern crate parent;

struct Local;

impl parent::A for Local { }
impl parent::C for Local {
    fn foo(&self) { }
}

تنفذ المحلية A و C لكن ليس B . إذا تم تنفيذ B محليًا ، فستتعارض ضمنته C مع الضمانة الشاملة غير القابلة للتخصيص وهي C for T where T: B .

صندوق parent v 1.1.0

// Same code as before, but add:
default impl<T> B for T where T: A { }

تمت إضافة هذا الضمني ، وهو عبارة عن ضمني متخصص تمامًا ، لذلك قلنا أنه تغيير غير منقطع. ومع ذلك ، فإنه ينشئ ضمنيًا متعدِّدًا - لدينا بالفعل "كل B impl C (غير قابل للتخصيص)" ، بإضافة "all A impl B (خاص)" ، لقد أضفنا ضمنيًا العبارة "all A impl C (غير قابل للتخصيص) ". الآن لا يمكن ترقية صندوق الطفل.


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

لذلك من غير مؤكد بالنسبة لي لو كان 'impls هي غير منقسمة "الضمان هو انقاذها، ولكن من المؤكد أنه غير متوافق مع سيطرة واضحة على مدى نهائية impl.

هل هناك أي خطة للسماح بذلك؟

struct Foo;

trait Bar {
    fn bar<T: Read>(stream: &T);
}

impl Bar for Foo {
    fn bar<T: Read>(stream: &T) {
        let stream = BufReader::new(stream);

        // Work with stream
    }

    fn bar<T: BufRead>(stream: &T) {
        // Work with stream
    }
}

لذلك ، بشكل أساسي تخصص لوظيفة القالب التي تحتوي على معلمة نوع مرتبطة بـ A حيث يكون الإصدار المتخصص مرتبطًا بـ B (والذي يتطلب A ).

torkleyy ليس حاليًا ولكن يمكنك القيام بذلك سرًا عن طريق إنشاء سمة يتم تنفيذها لكل من T: Read و T: BufRead وتحتوي على أجزاء من الكود الذي تريد التخصص فيه في الضمانات لهذه السمة. لا تحتاج حتى إلى أن تكون مرئية في واجهة برمجة التطبيقات العامة.

فيما يتعلق بمسألة التوافق العكسي ، أعتقد أنه بفضل القواعد اليتيمة يمكننا التخلص من هذه القواعد:

_Am الضمني متوافق مع الإصدارات السابقة للإضافة إلا إذا : _

  • _ السمة التي يتم تضمينها هي سمة تلقائية ._
  • _المتلقي هو معلمة نوع ، وكل سمة في الضمانة كانت موجودة مسبقًا.

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

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

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

فيما يتعلق بمسألة التوافق العكسي ، أعتقد أنه بفضل القواعد اليتيمة يمكننا التخلص من هذه القواعد:

_Am الضمني متوافق مع الإصدارات السابقة للإضافة إلا إذا : _

  • _ السمة التي يتم تضمينها هي سمة تلقائية ._
  • _المتلقي هو معلمة نوع ، وكل سمة في الضمانة كانت موجودة مسبقًا.

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

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

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

لقد اعتقدت في مرحلة ما أننا قد نكون قادرين على تخفيف القواعد اليتيمة حتى تتمكن من تنفيذ سمات لأنواع مثل Vec<MyType> ، ولكن إذا فعلنا ذلك ، فسيتم تنفيذ هذا الموقف بالطريقة نفسها تمامًا هناك:

//crate A

trait Foo { }

// new impl
// impl<T> Foo for Vec<T> { }
// crate B
extern crate A;

use A::Foo;

trait Bar {
    type Assoc;
}

// Sadly, this impl is not an orphan
impl<T> Bar for Vec<T> where Vec<T>: Foo {
    type Assoc = ();
}
// crate C

struct Baz;

// Therefore, this impl must remain an orphan
impl Bar for Vec<Baz> {
    type Assoc = bool;
}

withoutboats آه، فهمت لديك قائمة اثنين من الرصاص كما أو بدلا من و، والتي على ما يبدو هو ما كنت تعني؟

aturon نعم ، قصدت "أو" - هاتان هما الحالتان اللتان يمثل فيهما https://is.gd/k4Xtlp

هذا ما لم يكن يحتوي على أسماء جديدة. AFAIK هو ضمني يحتوي على اسم جديد لا ينكسر أبدًا.

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

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

لا أعلم أعتقد أن الأمر يعتمد على ما إذا كان هناك دافع قوي أم لا. يبدو من غير المحتمل بشكل خاص أن تدرك أن نوعًا ما يمكن أن يكون له unsafe impl Send/Sync بعد أن قمت بتحريره بالفعل أعتقد أن هذا سيكون آمنًا في معظم الأوقات ، ستكتب نوعًا بمعرفة مسبقة أنه سيكون آمنًا (لأن هذا هو الهدف من النوع).

أقوم بإضافة unsafe impl Send/Sync بعد الحقيقة طوال الوقت. أحيانًا لأنني أجعله آمنًا ، أحيانًا لأنني أدرك أن واجهة برمجة تطبيقات C التي أتفاعل معها جيدة لمشاركتها عبر سلاسل الرسائل ، وأحيانًا يكون ذلك بسبب ما إذا كان يجب أن يكون الشيء Send / Sync isn ما أفكر فيه عندما أقدم نوعًا ما.

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

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

trait Buffer: Read {
    type Buffered: BufRead;
    fn buffer(self) -> impl BufRead;
}

impl<T: Read> Buffer for T {
    default type Buffered = BufReader<T>;
    default fn buffer(self) -> BufReader<T> {
        BufReader::new(self)
    }
}

impl<T: BufRead> Buffer for T {
    type Buffered = Self;
    fn buffer(self) -> T {
        self
    }
}

هذا لأن النظام الحالي يتطلب أن يكون هذا الضمني صالحًا:

impl Buffer for SomeRead {
    type Buffered = SomeBufRead;
    // no overriding of fn buffer, it no longer returns Self::Buffered
}

ستطلق السمات impl Trait الكثير من الرغبة في هذا النوع من النمط ، لكنني أتساءل عما إذا كان هناك حل أفضل حيث يكون الضمني العام صالحًا ولكن هذا التخصص لا يعمل لأنه يقدم خطأ في النوع ؟

withoutboats نعم ، هذا أحد الأسئلة الرئيسية التي لم يتم حلها بشأن التصميم (والتي نسيت طرحها في المناقشات الأخيرة). هناك قدر لا بأس به من النقاش حول هذا الموضوع في سلسلة RFC الأصلية ، لكنني سأحاول كتابة ملخص للخيارات / المقايضات قريبًا.

aturon هل الحل الحالي هو الأكثر تحفظًا (متوافق مع ما نريد القيام به) أم أنه قرار يتعين علينا اتخاذه قبل الاستقرار؟

أنا شخصيًا أعتقد أن الحل الحقيقي الوحيد لهذه المشكلة الذي أثارته withoutboats هو السماح "بتجميع" العناصر معًا عند تحديد علامة default . إنه نوع من الحل الأفضل هو الأفضل ، لكنني أشعر أن البديل الأسوأ هو الأفضل (تجاوز أي وسيلة تتجاوز الكل) أسوأ قليلاً. (لكن في الواقع withoutboats بالطريقة التي كتبت بها هذا الرمز محيرة. أعتقد أنه بدلاً من استخدام impl BufRead كنوع إرجاع Buffer ، كنت تقصد Self::BufReader ، أليس كذلك؟)

في هذه الحالة ، يُسمح بما يلي:

trait Buffer: Read {
    type Buffered: BufRead;
    fn buffer(self) -> impl BufRead;
}

impl<T: Read> Buffer for T {
    default {
        type Buffered = BufReader<T>;
        fn buffer(self) -> BufReader<T> {
            BufReader::new(self)
        }
    }
}

impl<T: BufRead> Buffer for T {
    type Buffered = Self;
    fn buffer(self) -> T {
        self
    }
}

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

لكن في الواقع withoutboats ، الطريقة التي كتبت بها هذا الرمز محيرة. أعتقد أنه بدلاً من استخدام BufRead كنوع إرجاع من Buffer ، كنت تقصد Self :: BufReader ، أليس كذلك؟

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

ربما يكون شيئًا مثل نظام الكتابة لهذه اللغة مثيرًا للاهتمام أيضًا ، لأنه يبدو مشابهًا لـ Rusts ، لكن مع بعض الميزات ، قد تحل المشكلات الحالية.
( A <: B سيكون في Rust صحيحًا عندما يكون A عبارة عن بنية وتنفذ السمة B ، أو عندما يكون A سمة ، والتطبيقات العامة لكائنات هذه السمة موجودة ، على ما أعتقد)

يبدو أن هناك مشكلة في سمة Display للتخصص.
على سبيل المثال ، هذا المثال لا يجمع:

use std::fmt::Display;

pub trait Print {
    fn print(&self);
}

impl<T: Display> Print for T {
    default fn print(&self) {
        println!("Value: {}", self);
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

مع الخطأ التالي:

error[E0119]: conflicting implementations of trait `Print` for type `()`:
  --> src/main.rs:41:1
   |
35 |   impl<T: Display> Print for T {
   |  _- starting here...
36 | |     default fn print(&self) {
37 | |         println!("Value: {}", self);
38 | |     }
39 | | }
   | |_- ...ending here: first implementation here
40 | 
41 |   impl Print for () {
   |  _^ starting here...
42 | |     fn print(&self) {
43 | |         println!("No value");
44 | |     }
45 | | }
   | |_^ ...ending here: conflicting implementation for `()`

بينما هذا يجمع:

pub trait Print {
    fn print(&self);
}

impl<T: Default> Print for T {
    default fn print(&self) {
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

شكرا لإصلاح هذه المشكلة.

antoyo ، هل أنت متأكد من أن هذا هو السبب في أن Display خاص ، أم أن السبب Display لم يتم تطبيقه على tuples بينما Default هو؟

تضمين التغريدة
لا أعرف ما إذا كان السعر يقارب Display ، لكن ما يلي يعمل مع سمة Custom لم يتم تنفيذها لـ tuples:

pub trait Custom { }

impl<'a> Custom for &'a str { }

pub trait Print {
    fn print(&self);
}

impl<T: Custom> Print for T {
    default fn print(&self) {
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

بالمناسبة ، هذا هو الشيء الحقيقي الذي أريد تحقيقه بالتخصص:

pub trait Emit<C, R> {
    fn emit(callback: C, value: Self) -> R;
}

impl<C: Fn(Self) -> R, R, T> Emit<C, R> for T {
    default fn emit(callback: C, value: Self) -> R {
        callback(value)
    }
}

impl<C> Emit<C, C> for () {
    fn emit(callback: C, _value: Self) -> C {
        callback
    }
}

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

تحرير: أعتقد أنني اكتشفت سبب عدم تجميعها:
T في for T أكثر عمومية من () في for () لذلك لا يمكن أن يكون أول impl هو التخصص.
و C أكثر عمومية من C: Fn(Self) -> R لذا لا يمكن أن يكون impl هو التخصص.
من فضلك قل لي إذا كنت مخطئا.
لكن ما زلت لا أفهم لماذا لا يعمل مع المثال الأول مع Display .

هذا هو السلوك الصحيح حاليًا.

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

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

ولكن أيضًا ، نظرًا لأن () لا يتضمن العرض ، فهو ليس أكثر تحديدًا من T: Display . هذا هو السبب في أن التخصص لا يعمل ، بينما في الحالة الافتراضية ، (): Default ، لذلك فإن ذلك الضمني أكثر تحديدًا من T: Default .

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

لقد قدمت # 40582 لتتبع مشكلة السلامة المتعلقة بالحياة.

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

@ afonso360 لا ، هناك مشكلة منفصلة.

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

هل يمكن لأحد أن يوضح ما إذا كان هذا خطأ أم شيء ممنوع عمداً؟ https://is.gd/pBvefi

sgrif أعتقد أن المشكلة هنا هي أن إسقاط الأنواع المرتبطة بالتقصير غير مسموح به. يمكن أن تكون التشخيصات أفضل على الرغم من: https://github.com/rust-lang/rust/issues/33481

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

يشير هذا التعليق إلى أنه ضروري في بعض الحالات من أجل طلب السلامة (على الرغم من أنني لا أعرف السبب) وفي حالات أخرى لإجبار مستهلكي الواجهة على التعامل معها كنوع مجرد: https://github.com/rust- lang / rust / blob / e5e664f / src / librustc / traits / project.rs # L41

هل تمكّن أي شخص من الاطلاع على https://github.com/rust-lang/rust/issues/31844#issuecomment -266221638؟ يجب أن تكون هذه الضمانات صالحة مع التخصص بقدر ما أستطيع أن أقول. أعتقد أن هناك خطأ يمنعهم.

sgrif أعتقد أن مشكلة الكود الخاص بك قد تكون مشابهة للمشكلة في https://github.com/rust-lang/rust/issues/31844#issuecomment -284235369 والتي أوضحهاwithoutboats في https://github.com / rust-lang / rust / issue / 31844 # issuecomment -284268302. ومع ذلك ، بناءً على تعليق withoutboats ، يبدو أن المنطق المحلي الحالي يجب أن يسمح

جانبا ، حاولت تنفيذ ما يلي ولكن دون جدوى:

trait Optional<T> {
    fn into_option(self) -> Option<T>;
}

impl<R, T: Into<R>> Optional<R> for T {
    default fn into_option(self) -> Option<R> {
        Some(self.into())
    }
}

impl<R> Optional<R> for Option<R> {
    fn into_option(self) -> Option<R> {
        self
    }
}

توقعت حدسيًا أن يكون Option<R> أكثر تحديدًا من <R, T: Into<R>> T ، لكن بالطبع ، لا شيء يمنع impl<R> Into<R> for Option<R> في المستقبل.

لست متأكدًا من سبب عدم السماح بذلك. حتى إذا تمت إضافة impl<R> Into<R> for Option<R> في المستقبل ، ما زلت أتوقع أن يختار Rust التنفيذ غير default ، بقدر ما أستطيع أن أرى ، السماح لهذا الرمز ليس له أي تأثير على إعادة التوجيه- التوافق.

إجمالاً ، أجد أن العمل مع التخصص أمر محبط للغاية. فقط كل ما أتوقع أن يعمل ليس كذلك. الحالات الوحيدة التي نجحت فيها في التخصص هي تلك التي تكون بسيطة جدًا ، مثل الحصول على impl s تتضمن T where T: A و T where T: A + B . أجد صعوبة في تشغيل أشياء أخرى ، ولا تشير رسائل الخطأ إلى سبب عدم نجاح محاولات التخصص. بالطبع ، لا يزال هناك طريق أمامنا ، لذلك لا أتوقع رسائل خطأ مفيدة للغاية. ولكن يبدو أن هناك عددًا غير قليل من الحالات التي أتوقع فيها حقًا أن يعمل شيء ما (مثل أعلاه) ولكنه لا يعمل ، ومن الصعب جدًا حاليًا التأكد مما إذا كان ذلك لأنني أساء فهم ما هو مسموح به (والأهم من ذلك ، لماذا) ، إذا كان هناك شيء خاطئ ، أو إذا لم يتم تنفيذ شيء ما بعد. سيكون من المفيد جدًا إلقاء نظرة عامة لطيفة على ما يحدث مع هذه الميزة كما هي.

لست متأكدًا من أن هذا في المكان المناسب ، لكننا واجهتنا مشكلة في منتدى المستخدمين أود أن أذكرها هنا.

لا يتم تجميع الكود التالي (المقتبس من RFC هنا ) ليلاً:

#![feature(specialization)]

trait Example {
    type Output;
    fn generate(self) -> Self::Output;
}

default impl<T> Example for T {
    type Output = Box<T>;
    fn generate(self) -> Self::Output { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> Self::Output { self }
}

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

رابط إلى الموضوع هنا

@ burns47 هناك حل بديل مربك ولكنه مفيد هنا: https://github.com/rust-lang/rust/issues/31844#issuecomment -263175793.

dtolnay ليس مرضيًا تمامًا - ماذا لو كنا متخصصين في السمات التي لا

هل يمكن لأي شخص التعليق على ما إذا تم رفض الكود في العدد التالي عمداً؟ https://github.com/rust-lang/rust/issues/45542

هل يسمح التخصص بإضافة شيء مثل التالي إلى libcore؟

impl<T: Ord> Eq for T {}

impl<T: Ord> PartialEq for T {
    default fn eq(&self, other: &Self) -> bool {
        self.cmp(other) == Ordering::Equal
    }
}

impl<T: Ord> PartialOrd for T {
    default fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

بهذه الطريقة يمكنك تنفيذ Ord لنوعك المخصص والحصول على Eq و PartialEq و PartialOrd يتم تنفيذها تلقائيًا.

لاحظ أن تنفيذ Ord واشتقاق PartialEq أو PartialOrd أمر خطير ويمكن أن يؤدي إلى أخطاء خفية للغاية! باستخدام هذه الضمانات الافتراضية ، ستكون أقل إغراءً لاشتقاق تلك السمات ، لذلك سيتم تخفيف المشكلة إلى حد ما.


بدلاً من ذلك ، نقوم بتعديل الاشتقاق للاستفادة من التخصص. على سبيل المثال ، كتابة #[derive(PartialOrd)] أعلى struct Foo(String) يمكن أن يولد الكود التالي:

impl PartialOrd for Foo {
    default fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl PartialOrd for Foo where Foo: Ord {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

بهذه الطريقة يتم استخدام الضمانة الافتراضية إذا لم يتم تنفيذ Ord . ولكن إذا كان الأمر كذلك ، فإن PartialOrd يعتمد على Ord . لسوء الحظ ، لم يتم تجميع هذا: error[E0119]: conflicting implementations of trait `std::cmp::PartialOrd` for type `Foo`

stjepang آمل بالتأكيد أن تضاف البطانيات من هذا القبيل - impl<T:Copy> Clone for T أيضًا.

أعتقد

impl<T: Ord> PartialEq for T

يجب ان يكون

impl<T, U> PartialEq<U> for T where T : PartialOrd<U>

لأن PartialOrd يتطلب PartialEq ويمكنه أيضًا توفيره.

في الوقت الحالي ، لا يمكن للمرء حقًا استخدام الأنواع المرتبطة لتقييد التخصص ، وذلك لأنه لا يمكن تركها غير محددة ولأنها تؤدي إلى تكرار غير ضروري . انظر https://github.com/dhardy/rand/issues/18#issuecomment -358147645

في النهاية ، أحب أن أرى ما أسميه مجموعات التخصص مع بناء الجملة الذي اقترحه nikomatsakis هنا https://github.com/rust-lang/rust/issues/31844#issuecomment -249355377 وبشكل مستقل بواسطتي. أود كتابة RFC حول هذا الاقتراح لاحقًا عندما نكون أقرب إلى استقرار التخصص.

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

نظرًا لأن عمليات إغلاق النسخ قد تم تثبيتها بالفعل في الإصدار التجريبي ، أصبح لدى المطورين حافز أكبر لتحقيق الاستقرار في التخصص الآن. والسبب هو أن Fn و FnOnce + Clone يمثلان مجموعتين متداخلتين من الإغلاق ، وفي كثير من الحالات نحتاج إلى تطبيق سمات لكليهما.

فقط اكتشف أن صياغة rfc 2132 تشير إلى وجود 5 أنواع فقط من عمليات الإغلاق:

  • FnOnce (إغلاق move مع كون جميع المتغيرات الملتقطة ليست Copy ولا Clone )
  • FnOnce + Clone (إغلاق move مع كون جميع المتغيرات الملتقطة هي Clone )
  • FnOnce + Copy + Clone (إغلاق move مع كون جميع المتغيرات الملتقطة هي Copy وهكذا Clone )
  • FnMut + FnOnce (إغلاق بخلاف move مع المتغيرات الملتقطة المتغيرة)
  • Fn + FnMut + FnOnce + Copy + Clone (إغلاق بخلاف move بدون المتغيرات الملتقطة المتحولة)

لذا إذا لم تكن المواصفات متاحة في المستقبل القريب ، فربما يتعين علينا تحديث تعريفنا لسمات Fn حتى لا يتداخل Fn مع FnOnce + Clone ؟

أتفهم أن شخصًا ما قد نفذ بالفعل أنواعًا معينة هي Fn بدون Copy/Clone ، لكن هل يجب إيقاف هذا؟ أعتقد أن هناك دائمًا طريقة أفضل للقيام بنفس الشيء.

هل من المفترض أن يسمح التخصص بما يلي (لاحظ غياب default ) أم أنه خطأ؟

#![feature(specialization)]
mod ab {
    pub trait A {
        fn foo_a(&self) { println!("a"); }
    }

    pub trait B {
        fn foo_b(&self) { println!("b"); }
    }

    impl<T: A> B for T {
        fn foo_b(&self) { println!("ab"); }
    }

    impl<T: B> A for T {
        fn foo_a(&self) { println!("ba"); }
    }
}

use ab::B;

struct Foo;

impl B for Foo {}

fn main() {
    Foo.foo_b();
}

بدون تخصص ، يفشل هذا في البناء باستخدام:

error[E0119]: conflicting implementations of trait `ab::B` for type `Foo`:
  --> src/main.rs:24:1
   |
11 |     impl<T: A> B for T {
   |     ------------------ first implementation here
...
24 | impl B for Foo {}
   | ^^^^^^^^^^^^^^ conflicting implementation for `Foo`

فعلا؟ لا يوجد ضمني فارغ في المثال الخاص بي.

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

 impl B for Foo {}

MoSal ولكن هذا يعني "ليس فارغًا" لأن B يضيف طريقة بتطبيق افتراضي.

gnzlbg فارغ بالتعريف. لا شيء بين الأقواس.


#![feature(specialization)]

use std::borrow::Borrow;

#[derive(Debug)]
struct Bla {
    bla: Vec<Option<i32>>
}

// Why is this a conflict ?
impl From<i32> for Bla {
    fn from(i: i32) -> Self {
        Bla { bla: vec![Some(i)] }
    }
}

impl<B: Borrow<[i32]>> From<B> for Bla {
    default fn from(b: B) -> Self {
        Bla { bla: b.borrow().iter().map(|&i| Some(i)).collect() }
    }
}

fn main() {
    let b : Bla = [1, 2, 3].into();
    println!("{:?}", b);
}

error[E0119]: conflicting implementations of trait `std::convert::From<i32>` for type `Bla`:
  --> src/main.rs:17:1
   |
11 | impl From<i32> for Bla {
   | ---------------------- first implementation here
...
17 | impl<B: Borrow<[i32]>> From<B> for Bla {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Bla`
   |
   = note: upstream crates may add new impl of trait `std::borrow::Borrow<[i32]>` for type `i32` in future versions

ألن يمنع التخصص النزاعات المستقبلية المحتملة؟

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

alexreg انظر http://aturon.github.io/2018/04/05/sound-specialization/ للحصول على أحدث التطورات.

alexreg اتضح أن الصلاح _ صعب_. أعتقد أن هناك بعض العمل على فكرة "الضمانات القابلة للتطبيق دائمًا" التي تحدث حاليًا ، لذلك هناك تقدم. انظر https://github.com/rust-lang/rust/pull/49624. أيضًا ، أعتقد أن مجموعة عمل الطباشير تعمل على تنفيذ فكرة "الضمانات القابلة للتطبيق دائمًا" أيضًا ، لكنني لا أعرف إلى أي مدى وصل ذلك.

بعد قليل من الجدل ، يبدو أنه من الممكن التنفيذ الفعال لتضمينات التقاطع بالفعل عبر اختراق باستخدام specialization و overlapping_marker_traits .

https://play.rust-lang.org/؟gist=cb7244f41c040db41fc447d491031263&version=nightly&mode=debug

حاولت كتابة دالة متخصصة متكررة لتنفيذ ما يعادل كود C ++ هذا:


كود C ++

#include <cassert>
#include <vector>

template<typename T>
size_t count(T elem)
{
    return 1;
}

template<typename T>
size_t count(std::vector<T> vec)
{
    size_t n = 0;
    for (auto elem : vec)
    {
        n += count(elem);
    }
    return n;
}

int main()
{
    auto v1 = std::vector{1, 2, 3};
    assert(count(v1) == 3);

    auto v2 = std::vector{ std::vector{1, 2, 3}, std::vector{4, 5, 6} };
    assert(count(v2) == 6);

    return 0;
}


حاولت هذا:


كود الصدأ

#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

default impl<T> Count for T {
    fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();

        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![
        vec![1, 2, 3],
        vec![4, 5, 6],
    ];
    assert_eq!(v.count(), 6);
}


لكني أحصل على:

overflow evaluating the requirement `{integer}: Count`

لا أعتقد أن هذا يجب أن يحدث لأن impl<T> Count for T where T::Item: Count يجب ألا يتجاوز.

تحرير: آسف ، لقد رأيت للتو أن هذا قد تم ذكره بالفعل

Boiethios تعمل

#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

impl<T> Count for T {
    default fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();

        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![vec![1, 2, 3], vec![4, 5, 6]];
    assert_eq!(v.count(), 6);
}

هل لم يتم إصلاح فتحة السلامة بعد؟

alexreg لا أعتقد ذلك. انظر http://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/

أعتقد أن الجميع يركزون على الإصدار الآن ...

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

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

pythonesque هناك بعض المناقشات على https://github.com/rust-lang/rust/pull/42411

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

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

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

على أي حال ، ربما يكون هذا مجرد مؤشر على أن ما أريد القيام به هو غير مناسب لنظام السمات ويجب أن أتحول إلى آلية أخرى ، مثل وحدات الماكرو: P

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

يتطلب أحد البدائل للاستدلال السلبي أن يقوم النوع بتنفيذ سمة واحدة فقط من مجموعة مغلقة من السمات ، بحيث لا يمكن أن تتداخل التطبيقات مع السمات الأخرى في المجموعة (على سبيل المثال ، T ينفذ واحدًا من { Float | Int | Bool | Ptr } ).

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

في الوقت الحاضر كل ليلة ، هذا يعمل:

trait Foo {}
trait Bar {}

impl<T: Bar> Foo for T {}
impl Foo for () {}

ولكن حتى مع التخصص والاستخدام ليلا فإن هذا لا:

#![feature(specialization)]

trait Foo<F> {}
trait Bar<F> {}

default impl<F, T: Bar<F>> Foo<F> for T {}
impl<F> Foo<F> for () {}

هل لهذا سبب منطقي أم أنه خطأ؟

rmanoka أليست هذه مجرد قواعد اليتيم العادية؟ في الحالة الأولى ، لا يمكن للصندوق المتلقين للمعلومات أن impl Bar for () لذلك يسمح المترجم بذلك ، ولكن في المثال الثاني ، يمكن أن يتعارض صندوق المصب impl Bar<CustomType> for () والذي قد يتعارض مع الضمير الافتراضي الخاص بك.

Boscop في هذا السيناريو ، يجب على أي حال تجاوز impl Bar<bool> for () {} قبل الضمانات الأخرى ، فأنا أتوقع أن يعمل (وفقًا لـ RFC / التوقع). أليس هذا صحيحا؟

بالتعمق أكثر في خطوط المثال المضاد الذي ذكرته ، أدرك (أو أعتقد) أن المثال يفي باختبار "قابل للتطبيق دائمًا" ، وقد يتم العمل عليه.

ربما تعتمد هذه المشكلة على # 45814.

هل توجد أي خطط لدعم حدود السمات على التقصير غير الموجودة في التخصص؟

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

#![feature(specialization)]
trait Handler<M> {
    fn handle(&self, m:M);
}

struct Inner;
impl Handler<f64> for Inner {
    fn handle(&self, m : f64) {
        println!("inner got an f64={}", m);
    }
}

struct Struct<T>(T);
impl<T:Handler<M>, M:std::fmt::Debug> Handler<M> for Struct<T> {
    default fn handle(&self, m : M) {
        println!("got something else: {:?}", m);
        self.0.handle(m)
    }
}
impl<T> Handler<String> for Struct<T> {
    fn handle(&self, m : String) {
        println!("got a string={}", m);
    }
}
impl<T> Handler<u32> for Struct<T> {
    fn handle(&self, m : u32) {
        println!("got a u32={}", m);
    }
}

fn main() {
    let s = Struct(Inner);
    s.handle("hello".to_string());
    s.handle(5.0 as f64);
    s.handle(5 as u32);
}

علاوة على ذلك ، في المثال أعلاه ، هناك شيء غريب جربته - بعد إزالة السمة المقيدة على ضمنية Handler الافتراضية (وأيضًا self.0.handle (m)) يتم تجميع الكود بدون مشاكل. ومع ذلك ، عند إزالة تطبيق u32 ، يبدو أنه يكسر خصم السمة الأخرى:

#![feature(specialization)]
trait Handler<M> {
    fn handle(&self, m:M);
}

struct Struct<T>(T);
impl<T, M:std::fmt::Debug> Handler<M> for Struct<T> {
    default fn handle(&self, m : M) {
        println!("got something else: {:?}", m);
    }
}
impl<T> Handler<String> for Struct<T> {
    fn handle(&self, m : String) {
        println!("got a string={}", m);
    }
}
// impl<T> Handler<u32> for Struct<T> {
//     fn handle(&self, m : u32) {
//         println!("got a u32={}", m);
//     }
// }
fn main() {
    let s = Struct(());
    s.handle("hello".to_string());
    s.handle(5.0 as f64);
}

على الرغم من عدم وجود رمز يستدعي المعالج لـ u32 ، فإن عدم وجود التخصص يؤدي إلى عدم ترجمة الكود.

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

مع rustc 1.35.0-nightly (3de010678 2019-04-11) ، يعطي الكود التالي خطأ:

#![feature(specialization)]
trait MyTrait<T> {
    fn print(&self, parameter: T);
}

struct Message;

impl<T> MyTrait<T> for Message {
    default fn print(&self, parameter: T) {}
}

impl MyTrait<u8> for Message {
    fn print(&self, parameter: u8) {}
}

fn main() {
    let message = Message;
    message.print(1_u16);
}

خطأ:

error[E0308]: mismatched types
  --> src/main.rs:20:19
   |
18 |     message.print(1_u16);
   |                   ^^^^^ expected u8, found u16

ومع ذلك ، يتم تجميع الكود ويعمل عندما أحذف كتلة impl MyTrait<u8> :

#![feature(specialization)]
trait MyTrait<T> {
    fn print(&self, parameter: T);
}

struct Message;

impl<T> MyTrait<T> for Message {
    default fn print(&self, parameter: T) {}
}

/*
impl MyTrait<u8> for Message {
    fn print(&self, parameter: u8) {}
}
*/

fn main() {
    let message = Message;
    message.print(1_u16);
}

هل هذا حسب التصميم ، هل هذا لأن التنفيذ غير مكتمل ، أم أن هذا خطأ؟

أيضًا ، أود أن أعرف ما إذا كانت حالة الاستخدام هذه للتخصص (تنفيذ السمات مع معلمات النوع المتداخلة لنوع ملموس واحد بدلاً من تنفيذ نفس السمة للأنواع المتداخلة) سيتم دعمها. قراءة قسم "تحديد قواعد الأسبقية" في RFC 1210 ، أعتقد أنه سيكون مدعومًا ، لكن RFC لا يقدم مثل هذه الأمثلة ولا أعرف ما إذا كنا لا نزال نتبع RFC هذا بدقة.

الإبلاغ عن غرابة:

trait MyTrait {}
impl<E: std::error::Error> MyTrait for E {}

struct Foo {}
impl MyTrait for Foo {}  // OK

// But this one is conflicting with error message:
//
//   "... note: upstream crates may add new impl of trait `std::error::Error` for type
//    std::boxed::Box<(dyn std::error::Error + 'static)>` in future versions"
//
// impl MyTrait for Box<dyn std::error::Error> {}

لماذا يعتبر Box<dyn std::error::Error> مميزًا (تجنب استخدام كلمة "خاص") في هذه الحالة؟ حتى لو كان يتضمن std::error::Error في المستقبل ، فإن impl MyTrait for Box<dyn std::error::Error> لا يزال تخصصًا صالحًا لـ impl<E: std::error::Error> MyTrait for E ، أليس كذلك؟

لا يزال تخصصًا صالحًا

في حالتك ، لا يمكن impl<E: std::error::Error> MyTrait for E ، لأنه لا يحتوي على أي طرق default .

@ bjorn3 يبدو أنه يجب أن يعمل ، لكنه لا يعمل حتى إذا قمت بإضافة طرق وهمية

في الصندوق bar

pub trait Bar {}
impl<B: Bar> Bar for Box<B> {}

في الصندوق foo

#![feature(specialization)]

use bar::*;

trait Trait {
    fn func(&self) {}
}

impl<E: Bar> Trait for E {
    default fn func(&self) {}
}

struct Foo;
impl Trait for Foo {}  // OK

impl Trait for Box<dyn Bar> {} // Error error[E0119]: conflicting implementations of trait

لاحظ أنه إذا قمت بتغيير الصندوق bar إلى

pub trait Bar {}
impl<B: ?Sized + Bar> Bar for Box<B> {}

ثم تجميع الصناديق foo .

@ bjorn3 يبدو أننا لسنا بحاجة إلى طريقة default لتخصصها ( ملعب ).

KrishnaSannasi لا يمكنني إعادة إنتاج خطأ "التطبيقات المتعارضة" في ساحة اللعب ).

تحديث: أوه ، فهمت. يجب أن تكون السمة Bar من صندوق المنبع حتى يعمل المثال.

updogliu أنت مثال لا يظهر التخصص لأن Foo لا يطبق Error .

هل أبرمج متأخرًا جدًا الليلة ، أم لا ينبغي أن يتسبب ذلك في تجاوز سعة المكدس؟

#![feature(specialization)]
use std::fmt::Debug;

trait Print {
    fn print(self);
}

default impl<T> Print for [T; 1] where T: Debug {
    fn print(self) {
        println!("{:?}", self);
    }
}

impl<T> Print for [T; 1] where T: Debug + Clone {
    fn print(self) {
        println!("{:?}", self.clone());
    }
}

fn main() {
    let x = [0u8];
    x.print();
}

رابط الملعب

لطالما كانت كتل default impl ذات الحبيبات الخشنة تفعل أشياء غريبة جدًا بالنسبة لي ، أود أن أقترح تجربة بناء جملة التخصص الدقيق default fn بدلاً من ذلك.

تحرير: عند التحقق من RFC ، يكون هذا متوقعًا ، لأن default impl يعني في الواقع _not_ أن جميع العناصر في كتلة impl هي default ed أجد تلك الدلالات مفاجأة على أقل تقدير.

رابط الملعب

@ HadrienG2 لقد استخدمت دائمًا default fn في هذا المشروع ولكن هذه المرة نسيت الكلمة الرئيسية default واقترح المترجم إضافتها إلى impl . لم أشاهد مشكلة تكرار المكدس من قبل ولم يكن متأكدًا مما إذا كان متوقعًا في هذه المرحلة. شكرًا على الاقتراح ، default fn يعمل بشكل جيد.

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

قد لا يعمل النهج المقترح في RFC بشكل مباشر بعد الآن ، على الأقل ، بالنسبة لطرق الثابت المتأصلة:

// This compiles correctly today:
#![feature(specialization)] 
use std::marker::PhantomData;
struct Foo<T>(PhantomData<T>);
impl<T> Foo<T> {
    default const fn foo() -> Self { Self(PhantomData) }
    // ^^should't default here error?
}
// ----
// Adding this fails:
impl<T: Copy> Foo<T> {
    const fn foo() -> Self { Self(PhantomData) }
}

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

لأي شخص صادف هذا ولديه فضول بشأن الوضع - كان هناك تطوران مفاهديان مهمان في عام 2018:
http://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/
http://aturon.github.io/tech/2018/04/05/sound-specialization/

في الآونة الأخيرة ، كتب nikomatsakis الشهر الماضي (على سبيل المثال ، في سياق آخر ؛ أنا غامق) ما يلي:

كانت هناك مشكلة رئيسية واحدة [في التخصص] لم يتم حلها بشكل مرضٍ أبدًا ، وهي مشكلة تتعلق بالسلامة الفنية حول العمر والسمات [...] ثم [هاتان الوظيفتان المرتبطتان أعلاه]. يبدو أن هذه الأفكار قد حلت المشكلة بشكل أساسي ، لكننا كنا مشغولين في هذه الأثناء ولم يكن لدينا وقت للمتابعة.

يبدو الأمر متفائلًا على الرغم من أنه لا يزال هناك عمل يتعين القيام به.

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

مرحبا جميعا! هل يمكن لشخص أن يخبرني لماذا لا تعمل حالة الاستخدام هذه؟ البق أو السلوكيات المتوقعة؟

كهذا المثال . impl A for i32 ما يرام ، لكن لا يمكن تجميع impl A for () في 1.39.0 كل ليلة.

#![feature(specialization)]

trait A {
    fn a();
}

default impl <T: ToString> A for T {
    fn a() {}
}

impl A for i32 {
    fn a() {}
}

impl A for () {
    fn a() {}
}

تجميع الرسالة:

error[E0119]: conflicting implementations of trait `A` for type `()`:
  --> src/lib.rs:16:1
   |
8  | default impl <T: ToString> A for T {
   | ---------------------------------- first implementation here
...
16 | impl A for () {
   | ^^^^^^^^^^^^^ conflicting implementation for `()`
   |
   = note: upstream crates may add new impl of trait `std::fmt::Display` for type `()` in future versions

Hexilee ضع default على الطرق وليس الضمني.

KrishnaSannasi سبيل المثال 2

zserik نعم ، أعرف. لا أعتقد أنه تم تنفيذه بعد ، أو تم إسقاطه. على أي حال لا يعمل الآن.

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

أنا أطرح هذا السؤال هنا ، لأنني لم ألاحظ أن هذا الموضوع يظهر في أي مكان آخر - هل هناك أي خطط لـ default حدد وظائف مكتبة قياسية مختلفة ، على غرار الطريقة التي لدينا بها const -ified وظائف عندما يعتبر القيام بذلك آمنًا؟ السبب الرئيسي الذي أطالب به هو أن عمليات التنفيذ العامة الافتراضية From و Into ( impl<T, U: From<T>> Into<U> for T و impl<T> From<T> for T ) تجعل من الصعب كتابة From عام شامل Into التنفيذ من core ، وسيكون من الرائع لو تمكنت من تجاوز هذه التحويلات في الصناديق الخاصة بي.

حتى لو سمحنا بالتخصص مقابل From / Into فلن يساعد ذلك في الضمانات العامة بسبب مشكلة الشبكة.

KrishnaSannasi لا أعتقد أن هذا هو الحال. على سبيل المثال ، يجب أن يعمل هذا الرمز إذا كان From و Into قابلين للتخصيص ، لكن ليس لأنهم غير:

impl<M: Into<[S; 2]>, S> From<M> for GLVec2<S> {
    fn from(to_array: M) -> GLVec2<S> {
        unimplemented!()
    }
}
impl<M, S> Into<M> for GLVec2<S>
where
    [S; 2]: Into<M>,
{
    fn into(self) -> M {
        unimplemented!()
    }
}

pub struct GLVec2<S> {
    pub x: S,
    pub y: S,
}

ينجح ذلك إذا قمت بتحويل From و Into إلى سمة مخصصة لا تحتوي على تلك التطبيقات العامة: https://play.rust-lang.org/؟version=stable&mode=debug&edition= 2018 & gist = cc126b016ff62643946aebc6bab88c98

Osspial حسنًا ، إذا حاولت والمحاكاة باستخدام

https://play.rust-lang.org/؟version=nightly&mode=debug&edition=2018&gist=e5b9da0eeca05d063e2605135a0b5ead

سأكرر ، تغيير From/Into impl ليكون ضمناً افتراضيًا في المكتبة القياسية لن يجعل الضمانات العامة لـ Into ممكنة. (ولا يؤثر على الضمانات العامة لـ From )

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

هذا النمط بسيط - يمكننا تحديد هياكل مثل WithLabel أو WithID والتي تضيف بعض الحقول والطرق إلى الهياكل الأساسية ، لذلك على سبيل المثال إذا أنشأنا WithLabel<WithID<MyType>> فسنكون قادرين للحصول على id و label وجميع الحقول / الطرق بـ MyType أيضًا. لسوء الحظ ، مع التنفيذ الحالي ، هذا غير ممكن.

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

#![feature(specialization)]

use std::ops::Deref;
use std::ops::DerefMut;

// =================
// === WithLabel ===
// =================

struct WithLabel<T>(String, T);

pub trait HasLabel {
    fn label(&self) -> &String;
}

impl<T> HasLabel for WithLabel<T> {
    fn label(&self) -> &String { 
        &self.0
    }
}

// THIS SHOULD COMPILE, BUT GETS REJECTED
// impl<T> HasLabel for T
// where T: Deref, <Self as Deref>::Target : HasLabel {
//     default fn label(&self) -> &String { 
//         self.deref().label() 
//     }
// }

impl<T> Deref for WithLabel<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// ==============
// === WithID ===
// ==============

struct WithID<T>(i32, T);

pub trait HasID {
    fn id(&self) -> &i32;
}

impl<T> HasID for WithID<T> {
    fn id(&self) -> &i32 { 
        &self.0
    }
}

// THIS SHOULD COMPILE, BUT GETS REJECTED
// impl<T> HasID for T
// where T: Deref, <Self as Deref>::Target : HasID {
//     default fn id(&self) -> &i32 { 
//         self.deref().id() 
//     }
// }

impl<T> Deref for WithID<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// =============
// === Usage ===
// =============

struct A(i32);

type X = WithLabel<WithID<A>>;

fn test<T: HasID + HasLabel> (t: T) {
    println!("{:?}", t.label());
    println!("{:?}", t.id());
}

fn main() {
    let v1 = WithLabel("label1".to_string(), WithID(0, A(1)));
    // test(v1); // THIS IS EXAMPLE USE CASE WHICH DOES NOT COMPILE
}

لكي نجعل السطر test(v1) يعمل ، نحتاج إلى إضافة هذه السمة يدويًا:

impl<T: HasID> HasID for WithLabel<T> {
    fn id(&self) -> &i32 { 
        self.deref().id()
    }
}

بالطبع ، لإكمالها ، نحتاج أيضًا إلى جعل هذه السمة ضمنية:

impl<T: HasLabel> HasLabel for WithID<T> {
    fn label(&self) -> &String { 
        self.deref().label()
    }
}

وهذا سيء جدا . لنوعين فقط ، الأمر بسيط. ومع ذلك ، تخيل أن لدينا 10 تعريفات مختلفة للأنواع غير الشفافة ، والتي تضيف حقولًا مختلفة ، مثل WithID ، WithLabel ، WithCallback ، ... سمها ما شئت. مع السلوك الحالي للتخصصات ، سنحتاج إلى تحديد ... أكثر من 1000 تطبيق سمة مختلف! إذا تم قبول الكود الذي تم التعليق عليه ، فسنحتاج فقط إلى 10 تطبيقات للسمات وسيتطلب تنفيذ كل نوع جديد تنفيذًا إضافيًا واحدًا فقط .

لست متأكدًا من كيفية ارتباط شفرتك بالتخصص. لا تزال الوسيطة الخاصة بك (يتم تجميع الكود الأولي الخاص بك ولكن لا يتم ترجمة سطر test(v1); المعلق بدون الإشارة اليدوية التي قدمتها) إذا تمت إزالة أول سطر #![feature(specialization)] .

qnighy يجب تجميع الشفرة بعد إلغاء التعليق على الضمانات HasLabel for T و HasID for T - إنهم يستخدمون التخصص. في الوقت الحالي ، تم رفضهم (حاول إلغاء التعليق عليهم في الكود الذي قدمته!). هل يعقل لك الآن؟ 🙂

لنفكر في ثلاث حالات WithLabel<WithID<A>> ، WithID<WithLabel<A>> و WithLabel<WithLabel<A>> . ثم

  • الأول يغطي WithLabel<WithID<A>> و WithLabel<WithLabel<A>> .
  • تغطي الأداة الثانية WithID<WithLabel<A>> و WithLabel<WithLabel<A>> .

لذلك لا يفي زوج الضمانات بالشرط التالي من RFC :

للتأكد من أن التخصص متماسك ، سنضمن أنه لأي اثنين من التداخلات I و J التي تتداخل ، لدينا إما I < J أو J < I . أي ، يجب أن يكون المرء حقًا أكثر تحديدًا من الآخر.

وهي مشكلة حقيقية في حالتك أيضًا لأن الضمانة HasLabel لـ WithLabel<WithLabel<A>> يمكن تفسيرها بطريقتين.

كيف يمكننا تغطية هذه الحالة تمت مناقشته بالفعل

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

qnighy ، شكرًا على التفكير في الأمر.

وهي مشكلة حقيقية في حالتك أيضًا لأن علامة HasLabel الخاصة بـ WithLabel<WithLabel<A>> يمكن تفسيرها بطريقتين.

هذا صحيح إذا لم نعتبر أن impl<T> HasLabel for WithLabel<T> أكثر تخصصًا من impl<T> HasLabel for T لإدخال WithLabel<WithLabel<A>> . يغطي جزء RFC الذي قمت بلصقه بالفعل ، ومع ذلك ، أعتقد أن هذا يمثل قيدًا خطيرًا وسأطلب إعادة النظر في الدعم لحالة الاستخدام هذه في الإصدار الأول من هذا الامتداد.

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

الشيء الجيد هو أن كل شيء يتم تجميعه الآن (بما في ذلك التخصصات) ولكن ليس استخدام test(v1) :

#![feature(specialization)]
#![feature(optin_builtin_traits)]

use std::ops::Deref;
use std::ops::DerefMut;

// =================
// === WithLabel ===
// =================

struct WithLabel<T>(String, T);

auto trait IsNotWithLabel {}
impl<T> !IsNotWithLabel for WithLabel<T> {}

pub trait HasLabel {
    fn label(&self) -> &String;
}

impl<T> HasLabel for WithLabel<T> {
    fn label(&self) -> &String { 
        &self.0
    }
}

impl<T> HasLabel for T
where T: Deref + IsNotWithLabel, <Self as Deref>::Target : HasLabel {
    default fn label(&self) -> &String { 
        self.deref().label() 
    }
}

impl<T> Deref for WithLabel<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// ==============
// === WithID ===
// ==============

struct WithID<T>(i32, T);

pub trait HasID {
    fn id(&self) -> &i32;
}

impl<T> HasID for WithID<T> {
    fn id(&self) -> &i32 { 
        &self.0
    }
}

auto trait IsNotWithID {}
impl<T> !IsNotWithID for WithID<T> {}

impl<T> HasID for T
where T: Deref + IsNotWithID, <Self as Deref>::Target : HasID {
    default fn id(&self) -> &i32 { 
        self.deref().id() 
    }
}

impl<T> Deref for WithID<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// =============
// === Usage ===
// =============

struct A(i32);

type X = WithLabel<WithID<A>>;

fn test<T: HasID + HasLabel> (t: T) {
    println!("{:?}", t.label());
    println!("{:?}", t.id());
}

fn main() {
    let v1 = WithLabel("label1".to_string(), WithID(0, A(1)));
    test(v1);
}

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

https://play.rust-lang.org/؟version=nightly&mode=debug&edition=2018&gist=b66ee0021db73efaaa5d46edfb4f3990

qnighy لقد أنشأت مشكلة منفصلة حول هذا الخطأ: https://github.com/rust-lang/rust/issues/66041

حسنًا ، لقد اكتشفت للتو أن auto traits لن يكون حلاً هنا أبدًا ، كما (وفقًا لـ https://doc.rust-lang.org/nightly/unstable-book/language-features/optin-builtin-traits. html) تنتشر في جميع الحقول في البنية:

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

تعديل
qnighy لقد أغفلت بطريقة ما أنك قدمت رابطًا إلى الملعب. ❤️ شكرا جزيلا لك على ذلك. إنه يعمل وأنا مندهش من مدى دقة هذا الحل. العجيب أننا قادرون على التعبير عن ذلك حاليًا وأتمنى ألا يختفي هذا الاحتمال في المستقبل!

في مثل هذه الحالة ، يكون overlapping marker traits هو الاختراق الوحيد الذي يمكننا استخدامه الآن ، لكنني أعتقد أنه سيكون من الجيد السماح في المستقبل بنوع من الحلول الأسهل للتعبير عن أنواع غير شفافة (كما هو موضح في رسالتي السابقة: https : //github.com/rust-lang/rust/issues/31844#issuecomment-549023367).

مثال بسيط للغاية (تبسيط المثال أعلاه ) والذي يفشل:

trait Trait<T> {}
impl<T> Trait<T> for T {}
impl<T> Trait<()> for T {}

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

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

لأي شخص لا يعرف حتى الآن: هناك هذه الحيلة الرائعة التي

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

trait Trait {
    fn test(&self) { println!("default implementation"); }
}

impl<T> Trait for T {
    // violates DRY principle
    default fn test(&self) { println!("default implementation"); }
}

أقترح بناء الجملة التالي لإصلاح هذا (إذا كان يحتاج إلى إصلاح):

impl<T> Trait for T {
    // delegates to the already existing default implementation
    default fn test(&self);
}

انتقل إلى # 68309

jazzfool يرجى إعادة ونسق لي على هذه المسألة.

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

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

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

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

يمكنك اختبار ذلك باستخدام ماكرو. أنا صدئ قليلاً مع صدأ ، لكن شيئًا ما على هذا المنوال ...

[#cfg(test)]
static mut SPECIALIZATION_TRIGGERED : bool = false;

[#cfg(test)]
macro_rules! specialization_trigger {
    () =>  { SPECIALIZATION_TRIGGERED = true; };
}

[#cfg(not(test))]
macro_rules! specialization_trigger {
    () => {};
}

ثم استخدم specialization_trigger!() في الضمانة المتخصصة ، وفي الاختبارات استخدم assert!(SPECIALIZATION_TRIGGERED);

[#cfg(test)]
static mut SPECIALIZATION_TRIGGERED : bool = false;
...

سترغب في استخدام thread_local! { static VAR: Cell<bool> = Cell::new(false); } بدلاً من static mut لأنه بخلاف ذلك يمكن تعيين المتغير في مؤشر ترابط حالة اختبار واحد وقراءته عن طريق الخطأ من سلسلة رسائل أخرى. تذكر أيضًا إعادة تعيين المتغير في بداية كل اختبار ، وإلا ستحصل على true من الاختبار السابق.

لدي سؤال بخصوص نص RFC ، وآمل أن يكون هذا مكانًا جيدًا لطرحه.

في قسم إعادة الاستخدام ، يتم إعطاء هذا المثال:

trait Add<Rhs=Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
    fn add_assign(&mut self, rhs: Rhs);
}

// the `default` qualifier here means (1) not all items are implied
// and (2) those that are can be further specialized
default impl<T: Clone, Rhs> Add<Rhs> for T {
    fn add_assign(&mut self, rhs: Rhs) {
        let tmp = self.clone() + rhs;
        *self = tmp;
    }
}

أتساءل كيف يُفترض أن يتحقق هذا ، نظرًا لأن tmp لديه نوع Self::Output ولا يوجد شيء معروف عن هذا النوع المرتبط. لا يبدو أن نص RFC يشرح ذلك ، على الأقل ليس في أي مكان قريب من المكان المذكور في المثال.

هل هناك آلية معينة هنا من المفترض أن تنجح؟

هل يمكن تقييد هذا التقصير where T: Add<Output = T> ؟ أم أن هذه حلقة سببية؟

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

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

من فضلك لا تبطل التنفيذ. لا يزال المكسور وغير السليم وغير المكتمل يسمح بالتجريب.

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

من فضلك لا ، PyO3 (مكتبة ربط Python) تعتمد حاليًا على التخصص. راجع https://github.com/PyO3/pyo3/issues/210

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

Lucretiel نعم ، تعتمد العديد من التحسينات المفيدة (خاصة حول التكرارات) على التخصص ، لذلك سيكون من الانحدار التام لتطبيقه.

على سبيل المثال ، FusedIterator و TrustedLen غير مجدية بدون تخصص.

تعتمد مكتبة PyO3 (مكتبة ربط Python) حاليًا على التخصص

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

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

تعتمد العديد من التحسينات المفيدة (خاصة حول التكرارات) على التخصص ، لذلك سيكون من الانحدار التام لتنفيذها.

يعتمدون هذه الأيام على min_specialization (انظر على سبيل المثال https://github.com/rust-lang/rust/pull/71321) ، الذي يحتوي على أكبر فتحات سلامة موصولة.

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

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

أي فكرة ما هو الكود المقصود؟ اعتقدت في البداية أن الغرض من default impl هو تعيين type Output = Self; ، لكن هذا مستحيل في الواقع Output = T ؟

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

لم أتمكن من العثور على أي إشارة إلى min_specialization من مشكلة التتبع هذه خارج # 71321 PR - ووفقًا للكتاب غير المستقر ، فهذه هي مشكلة التتبع لهذه الميزة.

لا أعرف الكثير عن هذه الميزة أيضًا ، لقد رأيت للتو إصلاحات libstd السليمة. تم تقديمه في https://github.com/rust-lang/rust/pull/68970 والذي يشرح بعض الأشياء الأخرى حوله.

matthewjasper هل من المنطقي توثيق هذا أكثر قليلاً ومطالبة المستخدمين الليليين بـ feature(specialization) بالترحيل؟

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

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

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

أي فكرة ما هو الكود المقصود؟

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

default impl<T: Clone, Rhs> Add<Rhs> for T {
    type Output = T;

    fn add_assign(&mut self, rhs: Rhs) {
        let tmp = self.clone() + rhs;
        *self = tmp;
    }
}

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

من فضلك لا ، PyO3 (مكتبة ربط Python) تعتمد حاليًا على التخصص. راجع PyO3 / pyo3 # 210

عامل صيانة PyO3 هنا - نحن نفضل الابتعاد عن التخصص حتى نتمكن من الوصول إلى الصدأ المستقر. هل من المحتمل أن يستقر min_specialization قبل إتمام باقي التخصص؟

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

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

أعتقد أن هذا هو رابط YouTube الصحيح: https://youtu.be/uDbs_1LXqus
(أيضًا على هاتفي)

نعم ، هذا كل شيء. ها هو رابط للمناقشة المحددة: https://youtu.be/uDbs_1LXqus؟t=2073

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

#![feature(rustc_attrs, min_specialization)]

ثم إذا كنت بحاجة إلى إجراء سمة مسند التخصص كما هو موضح في الحد الأدنى من التخصص ، فقم بتمييز إعلان السمة بـ #[rustc_specialization_trait] .

تم الانتهاء من كل تخصصي في هذا الملف ، وهنا مثال على سمة مسند التخصص.

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

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

https://github.com/LLFourn/secp256kfun/blob/6766b60c02c99ca24f816801fe876fed79643c3a/secp256kfun/src/op.rs#L196 -L206

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

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

impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
{
    // no need for default
    fn extend(&mut self, iterable: T) {
        ...
    }
}

// We declare explicitly which impl we are specializing repeating all type bounds etc
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
    // And then we declare explicitly how we are making this impl narrower with ‘when’.
    // i.e. This impl is like the first except replace all occurances of ‘T’ with ‘&'a [A]’
    when<'a> T = &'a [A]
{
    fn extend(&mut self, iterable: &'a [A]) {
        ...
    }
}

شكرا على ملاحظاتك.

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

أجد صعوبة في مشاهدة مقاطع الفيديو ، لذلك لا يمكنني البحث عن الفيديو المرتبط ، ومع ذلك ، لدي سؤال واحد حول #[min_specialization] . كما هي ، هناك سمة rustc_unsafe_specialization_marker لسمات مثل FusedIterator التي توفر تلميحات تحسين ، بحيث يمكن التخصص فيها. matthewjasper كتب:

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

أفترض أن الخطة هي تنفيذ اقتراح aturon وإضافة طريقة تخصص لسمات مثل هذه ( where specialize(T: FusedIterator) ). ولكن في الوقت الحالي ، يبدو أن أي رمز يمكن أن يتخصص في هذه السمات . إذا استقرت كما هي ، يمكن للناس كتابة تخصصات مستقرة تعتمد عليها ، مما يعني أن هذا عدم الصحة سيستقر.

هل يجب أن يقتصر التخصص في هذه السمات أيضًا على المكتبة القياسية؟ هل تستمد المكتبة القياسية فائدة كافية من قدرتها على التخصص فيها؟

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

أفهم أن min_specialization as-ليس مخصصًا لتحقيق الاستقرار.

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

تخصص غير ضروري Copy :
https://github.com/rust-lang/rust/pull/72707/files#diff -3afa644e1d09503658d661130df65f59L1955

"تخصص" ليس:
https://github.com/rust-lang/rust/pull/71321/files#diff -da456bd3af6d94a9693e625ff7303113L1589

تنفيذ تم إنشاؤه بواسطة ماكرو ما لم يتم تمرير علامة تتجاوز الضمني الافتراضي:
https://github.com/rust-lang/rust/pull/73851/files؟file-filters٪5B٪5D=#diff -ebb36dd2ac01b28a3fff54a1382527ddR124

matthewjasper ، لا يبدو أن الرابط الأخير يرتبط بأي مقتطف محدد.

لست متأكدًا مما إذا كان هذا هدفًا واضحًا ، ولكن AIUI حقيقة أن الضمانات المتخصصة لم يتم وضع علامة عليها تمنحك طريقة لتجنب كسر التغييرات على الضمانات الشاملة. لا يتعارض default impl<T> Trait for T مع دلالات المصب - هؤلاء أصبحوا متخصصين للتو.

هل يمكن أن يكون تحذيرًا فقط إذا لم يتم تمييزه؟

كانت هناك حالات قليلة جدًا من التعليمات البرمجية في Rustc والمكتبة القياسية لا تفعل ما تبدو عليه لأنه لا توجد طريقة لمعرفة أن التخصص يحدث بالفعل

تجربتي مع جافا مماثلة (وإن لم تكن مماثلة تمامًا). قد يكون من الصعب معرفة أي فئة فرعية من الفصل تعمل بالفعل ...

نريد بعض العلامات على الضمانات المتخصصة أيضًا ، أيضًا من أجل الوضوح عند القراءة ، أليس كذلك؟

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

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

أعتقد أن الفرق قد يكون أكبر من أن يظهر التغيير. إنه يشير إلى هذا: https://github.com/rust-lang/rust/blob/fb818d4321dee29e1938c002c1ff79b0e7eaadff/src/librustc_span/def_id.rs#L124

رد: بطانية تشير إلى أنهم يكسرون التغييرات على أي حال:

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

    • نحن نكسر إشارات الشعوب (ما يفعله min_specialization ).

    • نطلب منهم أن يعلقوا بطريقة ما حدود سماتهم على أنها قابلة للتطبيق دائمًا عند الضرورة.

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

cuviper في الواقع ، أشعر أنه لا تزال هناك حالات متطرفة حول إضافة ملحقات بطانية جديدة ، حتى مع التخصص. أتذكر أنني كنت أحاول معرفة ما الذي يتطلبه الأمر للسماح لنا بإضافة impl<T: Copy> Clone for T { } imp لقد كتبت منشور

بغض النظر ، يمكننا جعله تحذيرًا بسيطًا لعدم وجود تعليق توضيحي #[override] .

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

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

كانت هناك حالات قليلة جدًا من التعليمات البرمجية في Rustc والمكتبة القياسية لا تفعل ما تبدو عليه لأنه لا توجد طريقة لمعرفة أن التخصص يحدث بالفعل

تجربتي مع جافا مماثلة (وإن لم تكن مماثلة تمامًا). قد يكون من الصعب معرفة أي فئة فرعية من الفصل تعمل بالفعل ...

مرة أخرى في مايو ، اقترحت بديلاً للتخصص في IRLO لا يعتمد فعليًا على الضمانات المتداخلة ، ولكنه يسمح بدلاً من ذلك بتضمين واحد لـ where match على معلمة النوع الخاص به:

impl<R, T> AddAssign<R> for T {
    fn add_assign(&mut self, rhs: R) where match T {
        T: AddAssignSpec<R> => self.add_assign(rhs),
        T: Add<R> + Copy => *self = *self + rhs,
        T: Add<R> + Clone => { let tmp = self.clone() + rhs; *self = tmp; }
    }
}

يمكن بعد ذلك استخدام الصناديق المتلقية للمعلومات مثل impl لتنفيذ "التخصص" ، لأنه حسب الاصطلاح مثل الضمنية للسمة Trait ستتطابق أولاً مع الأنواع التي تنفذ سمة أخرى TraitSpec ، وأنواع المصب سيكون قادرًا على تنفيذ تلك السمة لتجاوز السلوك العام:

// Crate upstream
pub trait Foo { fn foo(); }
pub trait FooSpec { fn foo(); }

impl<T> Foo for T {
    fn foo() where T {
        T : FooSpec => T::foo(),
        _ => { println!("generic implementation") }
    }
}

fn foo<T : Foo>(t: T) {
    T::foo()
}

// crate downstream
struct A {}
struct B {}

impl upstream::FooSpec for A {
    fn foo() { println!("Specialized"); }
}

fn main() {
    upstream::foo(A); // prints "specialized"
    upstream::foo(B); // prints "generic"
}

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

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

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

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

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

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

@ the8472nikomatsakis، @ الظلام الفيلق: شكرا لردود الفعل الإيجابية! أحاول الرد على بعض ملاحظاتك في سلسلة IRLO ، لأنني لا أريد أن أكون مزعجًا جدًا بشأن مشكلة التتبع (أنا آسف لكل واحد منكم توقع أخبارًا عن التخصص ووجد للتو ما أشتاكه: مسح :).

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

أنا أيضًا أؤيد وجود نوع من العلامات على الضمانات المتخصصة.

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

وإلا ، فإن الكلمة الرئيسية الحالية الوحيدة التي تبدو ... حسنًا .. مناسبة كمحدد ، قد تكون super ؟

ملخص بإعادة استخدام مثال LLFourn من https://github.com/rust-lang/rust/issues/31844#issuecomment -639977601:

  • super (محجوز بالفعل ، ولكن قد يُساء تفسيره أيضًا كبديل لـ default )
super impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • specialize
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • spec (اختصار ل specialize مثل impl هو implement ) (قلق قائم من قبلssokolow التي أثيرت في https://github.com/rust-lang / rust / issues / 31844 # issuecomment-690980762)
spec impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • override (محجوز بالفعل ، شكرًا @ the8472 https://github.com/rust-lang/rust/issues/31844#issuecomment-691042082)
override impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>

الكلمات الرئيسية المحجوزة موجودة هنا

أو spec (اختصار specialize مثل impl لـ implement )

أصبحت "المواصفات" مألوفة أكثر لدى الناس كاختصار لـ "المواصفات" (على سبيل المثال ، "مواصفات HTML 5") لذلك لا أعتقد أنها ستكون اختصارًا جيدًا لـ "التخصص".

override هي كلمة مفتاحية محجوزة ، أفترض أنها مخصصة للوظائف ، لذلك قد تكون قابلة للاستخدام في كتلة ضمنية

التخصص يعتمد أيضًا على اللغة - باعتباري أستراليًا فهو متخصص بالنسبة لي ، لذا فإن استخدام "المواصفات" يزيل الغموض المحلي.

التخصص يعتمد أيضًا على اللغة - باعتباري أستراليًا فهو متخصص بالنسبة لي ، لذا فإن استخدام "المواصفات" يزيل الغموض المحلي.

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

بصفتي كنديًا ، يجب أن أقول إن التخصص / التخصص ليس هو الكلمة الوحيدة المستخدمة في البرمجة التي تختلف باختلاف اللغة.

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

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

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

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

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

ضع بشكل مختلف: كل مصطلح مستخدم في Rust يعتمد على اللغة.

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

استخدم special ككلمة رئيسية؟

بدلاً من ذلك ، اصنع كلمتين رئيسيتين: specialize و specialise واجعلهما متساويين ...

(أو يمكنك أن تتعلم الهجاء غير الأمريكيين بطريقة صحيحة: نحن: 😂)

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

@ mark-im لسوء الحظ ، هذا منحدر زلق يؤدي إلى حجج مفادها أن Rust يجب أن يكون لديه مجموعات بديلة من الكلمات الرئيسية في كل لغة رئيسية قد يأتي المتعلمون منها.

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

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

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

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

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

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

كل هذا يقال ، سأكون سعيدًا جدًا بـ "هل تقصد XXX؟" نهج رسالة الخطأ. الكلمات المحايدة التي لا تحتوي على مشاكل أخرى جيدة أيضًا.

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

يعيش حوالي 70٪ من الناطقين باللغة الإنجليزية في بلدان تستخدم تهجئة أمريكية.

أيضا..

"غالبًا ما يُنظر إلى التهجئة -ize بشكل غير صحيح على أنها أميركية في بريطانيا. وهي مستخدمة منذ القرن الخامس عشر ، تسبق -ise بأكثر من قرن. -ize تأتي مباشرة من اليونانية -ιζειν -izein واللاتينية -izāre ، بينما - ise يأتي عبر French -iser. يوصي قاموس أوكسفورد الإنجليزي (OED) بـ -ize وسرد نموذج -ise كبديل. "

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

المرجع. https://en.wikipedia.org/wiki/American_and_British_English_spelling_differences# -ise، _- ize _ (- isation، _- ization)

يبدو أن الإسبانية والإيطالية لديهما من الألف إلى الياء أو اثنين ، لذا لست متأكدًا من أين يحصل الفرنسيون على -iser ، ربما من الألمانية؟

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

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