Rust: 专业化跟踪问题(RFC 1210)

创建于 2016-02-23  ·  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)
  • []与关联的const集成
  • []界限不一定总是正确执行(https://github.com/rust-lang/rust/issues/33017)
  • []如果父级没有default成员,我们是否应该允许空impls? https://github.com/rust-lang/rust/issues/48444
  • []实施“始终适用”,表示https://github.com/rust-lang/rust/issues/48538
  • []描述并测试围绕创建专业化图的精确循环条件(请参阅此注释

RFC中尚未解决的问题:

  • 关联类型是否应该可以专门化?
  • 投影何时应显示default type ? 从来没有在Typeck期间? 还是单态的?
  • 是否应该将默认特征项目视为default (即可特殊化)?
  • 我们应该拥有default impl (所有项目均为default )还是partial impl (其中default为启用); 有关default impl受到限制的一些相关示例,请参见https://github.com/rust-lang/rust/issues/37653#issuecomment -616116577。
  • 我们应该如何处理终生可调度性?

请注意,当前实现的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)]

然后,如果需要按照最大最小专业化中的说明制作_specialization predicate_特征,则可以用#[rustc_specialization_trait]标记特征声明。

我所有的专业化都在此文件中完成,的示例

该功能可以正常工作并满足我的需求。 显然,这是使用锈迹斑斑的内部标记,因此很容易在没有警告的情况下破裂。

反馈的唯一负面影响是我认为default关键字没有意义。 从本质上说, default现在的意思是:“此impl是可特殊化的,因此将包含此一子集的impls解释为它的特殊化,而不是冲突的impl”。 问题是它导致看起来很奇怪的代码:

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

在这里,第二个impl专用于第一个,但它也是defaultdefault的含义似乎丢失了。 如果您看一下其余的impls,很难找出哪个impls专门研究哪个。 此外,当我做出与现有的重叠的错误提示时,通常很难弄清楚我哪里出了错。

在我看来,如果一切都可以专门化,并且当您对某项进行专门化时,则可以精确地声明要专门化的暗示,这将变得更加简单。 将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中的“链式规则”扩展到更具表现力的内容,例如所谓的“格子规则”?
  • 与上述两种情况相关,否定推理如何适合故事? 我们是否可以通过巧妙地使用专业化/孤立规则来恢复我们所需的否定推理,还是应该使它变得更一流?

我不确定专业化是否会更改孤立规则:

  • “链接”孤立规则必须保持不变,因为否则您将没有安全的链接。
  • 我认为“未来兼容性”孤立规则不会改变。 在您的下方添加非专业化的impl仍然是一项重大更改。

更糟糕的是,“未来兼容性”孤立规则将跨板条的专业化置于相当严格的控制之下。 没有它们,默认暗示使他们的方法保持开放状态会变得更加糟糕。

我从不喜欢明确的负面推理。 我认为总的负面推理专业化是一个不错的折衷方案。

实施时是否应允许使用专门的含义? 还是我错过了什么?
http://is.gd/3Ul0pe

与此相同,希望它可以编译: http :

涉及关联类型时,在确定重叠方面似乎有些怪异。 它将编译为: http : http :

这是我希望专门编译的一段代码:

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不是一对(因此它与第一个impl匹配,但与第二个impl不匹配)。
  • 您可能拥有(T, U) ,其中:

    • T: FromSqlRow<ST, DB>

    • U: FromSqlRow<SU, DB> ,但_not_

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

    • (因此第二个impl匹配,但第一个不匹配)

  • 这两个impls重叠,因为您可以使用(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>

但是,两个impl都不是更具体的:

  • 您可以使用T来使T: FromSqlRow<ST, DB>T不是Option<U> (匹配第一个impl,但不匹配第二个)
  • 您可以使用Option<T>来使T: Queryable<ST, DB>而不是Option<T>: FromSqlRow<Nullable<ST>, DB>

@SergioBenitez

编译失败,编译器援引实现冲突。 请注意, &str不实现FromStr ,因此不应有冲突。

问题是编译器保守地假设&str将来可能会实现FromStr 。 对于本示例来说,这似乎很愚蠢,但是总的来说,我们一直都在添加新的impls,并且当添加这些impls时,我们希望保护下游代码不被破坏。

这是一个保守的选择,随着时间的推移,我们可能希望放松一下。 您可以在此处获取背景:

感谢您澄清这两种情况。 现在完全有意义

在2016年3月22日星期二,下午6:34 Aaron Turon [email protected]写道:

@SergioBenitez https://github.com/SergioBenitez

编译失败,编译器援引实现冲突。 注意
&str没有实现FromStr,所以不应该有冲突。

问题是编译器保守地假定&str
将来可能会实现FromStr。 这似乎很愚蠢
此示例,但总的来说,我们一直都在添加新的impls,我们希望
添加这些impls时,可以保护下游代码不被破坏。

这是一个保守的选择,我们可能想放松一下
随着时间的推移。 您可以在此处获取背景:

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

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件或在GitHub上查看
https://github.com/rust-lang/rust/issues/31844#issuecomment -200093757

@aturon

问题是编译器保守地假设&str将来可能会实现FromStr。 对于本示例来说,这似乎很愚蠢,但是总的来说,我们一直都在添加新的impls,并且当添加这些impls时,我们希望保护下游代码不被破坏。

这不是专业化要解决的问题吗? 随着专业化,我希望即使的实施FromStr&str在未来加入,直接执行的Simple特质为&str将优先。

@SergioBenitez,您需要将default fn放到更一般的展示中。 您的
例子不是专门化的。

2016年3月22日星期二,下午6:54塞尔吉奥·贝尼特斯[email protected]
写道:

@aturon https://github.com/aturon

问题是编译器保守地假定&str
将来可能会实现FromStr。 这可能看起来很愚蠢
例如,但总的来说,我们一直都在添加新的展示次数,
添加这些impls时,可以保护下游代码不被破坏。

这不是专业化要解决的问题吗? 用
专业化,我希望即使FromStr的实现
对于&str在以后添加的情况下,
&str的特征优先。

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件或在GitHub上查看
https://github.com/rust-lang/rust/issues/31844#issuecomment -200097995

我认为被自动认为default “默认”特征项目听起来令人困惑。 您可能既希望像Haskell这样的特征同时具有参数性,又要简化impl s。 你也不能轻易grep他们喜欢你可以为default 。 键入default关键字并提供默认实现并不难,但是不能按原样将它们分开。 另外,如果要澄清语言,则可以将这些“默认”特征项目重命名为文档中的“提议特征”项目。

请注意#32999(注释) :如果我们遵循格规则(或允许使用负约束),则“使用中间特征”技巧以防止某些事物的进一步专业化将不再起作用。

@Stebalien

为什么不起作用? 诀窍将专业化限于私人特质。 如果无法访问私有特征,则不能对其进行专门化处理。

@ arielb1啊。 好点子。 就我而言,特征不是私有的。

我认为“外部人不能专长是因为孤儿前向兼容性+一致性规则”,这种推理不是特别有趣或有用。 尤其是当我们不遵守特定的一致性规则时。

有没有办法访问覆盖的default impl ? 如果是这样,这将有助于构建测试。 请参阅按合同设计libhoare

在类型检查期间允许投影默认的关联类型将允许在编译时强制类型不平等: https :

#![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的评论编辑

只有fyi @rphmeier可能应该避免is.gd,因为由于使用CloudFlare而无法为Tor用户解决。 GitHub可以与完整URL一起正常工作。 而且play.rust-lang.org在Tor上可以正常工作。

@burdges FWIW play.rust-lang.org本身的“ Shorten”按钮使用了is.gd。

但是它可能可以更改: https :

像这样使用(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支持此功能,并且目前还有其他实现方式吗?

@zitsen

当前的专业化设计不允许使用这些impls,因为T: Foo + BarT: Foo + Baz都不比另一个更专业。 也就是说,如果您有一些T: Foo + Bar + Baz ,则不清楚哪个impl应该“获胜”。

我们对一个更具表现力的系统有一些想法,该系统将允许您也为T: Foo + Bar + Baz赋予一个隐含含义,从而使之模棱两可,但是尚未完全提出。

如果否定性特征限制了trait Baz: !Bar的土地,那也可以与专门化一起使用,以证明实现Bar的类型集和实现Baz的类型集是不同的且可以分别进行专业化。

似乎@rphmeier的答复正是我真正想要的,建议T: Foo + Bar + Baz也会有所帮助。

只是忽略这一点,我仍然与我的情况有关,并且对于specialization和其他功能的推出总是感到兴奋。

谢谢@aturon @rphmeier

我最近一直在研究专业化,并且遇到了这个奇怪的案例:

#![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 {
   |> ^

围栏

我相信上面的_should_可以编译,并且实际上有两个有趣的变体可以按预期工作:

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是在单独的板条箱(!)中定义的,则不能使用,请参见此示例repo

我也相信这个问题可能与#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)

这似乎与您的案例足够相关。

@aatch该示例似乎不符合示例特征的直观定义: https : //play.rust-lang.org/?gist=97ff3c2f7f3e50bd3aef000dbfa2ca4e&version=nightly&backtrace =0

专业化代码明确禁止这样做-参见#33481,我最初认为这是一个错误,但原来是诊断问题。 我的PR改善了这里的诊断能力,却没有引起人们的注意,而且我已经有相当长的一段时间没有将它们维持在最新的Master上了。

@rphmeier RFC文本建议尽管允许复制,但该示例是从中复制的。

我玩过一些可以从专业化中受益的代码。 我强烈认为我们应该采用晶格法则而不是链接法-感觉很自然,并且是获得我所需要的灵活性的唯一方法。

如果我们在impl以及单个项目上default ,我们是否可以强制说如果任何项目被覆盖,那么它们都必须是? 这将使我们能够基于其他项目中默认assoc类型(例如)的精确类型进行推理,这似乎在表达能力方面很有用。

应该允许以下情况吗? 我想专门化一个类型,以便ArrayVec的元素类型为Copy时为Copy ,否则它具有析构函数。 我正在尝试通过使用被专业化代替的内部字段来实现这一目标。

我希望可以进行编译,即从A: Copy + Array绑定(操场上的可编译代码段)选择的字段类型中推断ArrayVec<A>字段的可复制性。

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

不需要带注释的where子句,因为它在公共接口中公开了私有类型Repr 。 (无论如何,它还是ICE)。

编辑:我已经忘了我已经报告了有关此问题的问题#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");

@tomaka @Aatch

那里的问题是不允许您依赖其他默认项的值。 因此,当您有此提示时:

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的默认组实现时,这实际上将是一种简化形式。 (不过,我不确定是否有一种方法可以纯粹在隐含中做到这一点。)

附言:很抱歉回复您的邮件很长时间,我看到的日期是_July_。

虽然impl Trait确实有帮助,但是尚没有接受或实现的RFC允许它以任何形式与特征主体一起使用,因此,寻找该RFC感觉有些奇怪。

我对实现default impl功能(其中所有项目均为default )感兴趣。
您愿意为此捐款吗?

@giannicic绝对! 我也很乐意帮助指导这项工作。

当前是否有关于关联类型是否应该专门化的结论?

以下是我的用例的简化,表明需要专用的关联类型。
我有一个通用的数据结构,例如Foo ,它可以协调容器特征对象的集合( &trait::Property )。 特质trait::PropertyProperty<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”属性(然后将new属性与impl项目的“ defaultness”属性一起使用)来完成这项工作的,但该操作也很快捷
可能包括在解析期间将default设置为default impl所有隐含项目。
对我来说,这不是一个“完整”的解决方案,因为我们丢失了“默认”与隐含性相关,而不与隐含性相关的信息,
另外,如果计划引入partial impl则第一个解决方案将已经提供了可用于存储defaultpartial的属性。 但是只是为了确保
不浪费时间,您怎么看?

@giannicic @aturon我可以建议我们创建一个特定的问题来讨论default impl吗?

没关系,我创建了一个: https :

给定以下条件,晶格规则是否可以允许我:

trait Foo {}

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

为实现ABC ,...的某种组合的类型的子集添加Foo的实现:

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) ,以避免实现冲突。

@gnzlbg

并允许我“禁止”某些组合,例如A + C永远不会发生:

不,这不是晶格规则所允许的。 在某种形式或种类上,这将更多地是“消极推理”的领域。

上下文:在为所有都是特征的各种形状(点,立方体,多边形等)实现ApproxEqual(Shape,Shape)特征时,我陷入了这种渴望。 我必须通过将其重构为不同的特征(例如ApproxEqualPoint(Point,Point))来解决此问题,以避免实现方面的冲突。

因此, @ withoutboats一直在提倡“排除组”的概念,您可以在其中声明某些特性是互斥的(即,您最多可以实现其中的一个)。 我设想这就像枚举(即所有特征都一起声明)一样。 我喜欢这个主意,尤其是因为(我认为!)它有助于避免负面推理的一些更有害的方面。 但是我觉得在这方面还需要更多的思考-还有一篇很好的文章,试图总结关于如何思考消极推理的所有“数据”。 也许现在,我(大部分)总结了我的HKT和专业系列,我可以考虑一下...

@nikomatsakis

因此, @ withoutboats一直在提倡“排除组”的概念,您可以在其中声明某些特性是互斥的(即,您最多可以实现其中的一个)。 我设想这就像枚举(即所有特征都一起声明)一样。 我喜欢这个主意,尤其是因为(我认为!)它有助于避免负面推理的一些更有害的方面。 但是我觉得在这方面还需要更多的思考-还有一篇很好的文章,试图总结关于如何思考消极推理的所有“数据”。 也许现在,我(大部分)总结了我的HKT和专业系列,我可以考虑一下...

我在编写此代码时考虑了排除组(您在前几天在论坛上提到了它),但是我认为它们无法工作,因为在此特定示例中,并非所有特征实现都是排他的。 最简单的例子是PointFloat特性: Float _can_是一维点,因此ApproxEqualPoint(Point, Point)ApproxEqualFloat(Float, Float)不能是独家。 还有其他示例,例如SquarePolygonBox |。 CubeAABB (轴对齐的边界框),其中的“特征层次”实际上需要更复杂的约束。

不,这不是晶格规则所允许的。 在某种形式或种类上,这将更多地是“消极推理”的领域。

我至少能够实现特定的情况,并在其中放入unimplemented!() 。 这就足够了,但是显然如果编译器会静态捕获那些我调用其中带有unimplemented!()的函数的情况(在这一点上,我们再次处于否定推理的情况),我将更愿意这样做。

@gnzlbg晶格专业化可以让您cry:。

“排除组”的概念实际上只是负的特征边界。 我们尚未深入探讨的一件事是反极性专业化的概念-允许您编写一个极性相反的专门化的impl为其不太专业化的impl。 例如,在这种情况下,您将只写:

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

我不能完全确定允许这样做的含义。 我认为这与Niko已经强调的问题有关,即专业化现在是如何将代码重用与多态相结合的。

通过对否定推理和否定暗示的所有讨论,我感到不得不重新提出旧的Haskell关于“实例链”的想法(GHC问题跟踪器Rust pre-RFC ),这可能是灵感的潜在来源。其他。

本质上,这个想法是,在任何可以编写trait impl的地方,您还可以编写任意数量的“ else if子句”,指定其他$$$ impl适用于以前的情况。否,则使用一个可选的最终“ else子句”指定否定的隐含含义(即,如果Trait的所有子句都不适用,则!Trait适用)。

@withoutboats

“排除组”的概念实际上只是负的特征边界。

我认为这对于我的用例就足够了。

我认为这与Niko已经强调的问题有关,即专业化现在是如何将代码重用与多态相结合的。

我不知道这些是否可以解决。 我希望有:

  • 多态性:单个特征,它为许多不同类型抽象了操作的不同实现,
  • 代码重用:我不想为每种类型实现该操作,而是为实现某些特征的类型组实现它们,
  • 性能:能够针对特定类型或类型子集覆盖已存在的实现,该实现具有比已存在的实现更特定的一组约束,
  • 生产力:能够递增地编写和测试我的程序,而不必添加很多impl进行编译。

涵盖所有情况很难,但是如果编译器强迫我涵盖所有情况:

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

我可以在需要时逐步添加impls,并且在尝试使用不具有impl类型的trait时也得到不错的编译器错误。

我不能完全确定允许这样做的含义。

Niko提到否定推理存在问题。 FWIW在上面的示例中使用否定推理的唯一目的是声明用户知道特定情况下需要使用隐式实现,但已明确决定不为其提供实现。

我只是点击了#33017,还没有看到它在这里链接。 它被标记为一个健全的漏洞,因此在此处进行跟踪将非常有用。

对于https://github.com/dtolnay/quote/issues/7,我需要与RFC中的此示例相似的内容,但该功能尚不可用。 cc @tomaka @Aatch @rphmeier对此发表了评论。

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并需要菱形图案。 这是我的解决方案。 cc @zitsen之前曾问过这个问题, @ 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相似,在这种情况下,程序会在没有专门的impl的情况下进行编译,并且在添加时会出现类型错误。 cc @bluss之前曾关注过这种情况。

如果我们允许在一个单独的箱子中不使用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的迭代器impl。 也许我们不想强迫您这样做-因此,没有default本地专业化。


我想的问题是, default确切作用是什么。 我认为,要求default最初是朝着明确性和自我记录代码的方向发展。 正如Rust代码默认情况下是不可变的,默认情况下是私有的,默认情况下是安全的一样,默认情况下它也应该是最终的。 然而,由于“非终局”是一个全球性的属性,不能,除非我让专注一个项目专门的项目。

我认为,要求default最初是朝着明确性和自我记录代码的方向发展。 但是,[..]我无法将其专门化,除非让您将其专门化。

那真的很糟糕吗? 如果您想专门从事展示,那么其他人可能也会想。

我担心,因为仅考虑该RFC已经给我提供了使用CSD代码库工作的PTSD闪回,这些代码库使用了令人讨厌的重载和继承,并且不知道wtf正在其中有方法调用的任何代码行中进行。 我非常感谢@aturon为使专业化明确和自我记录所

那真的很糟糕吗? 如果您想专门从事展示,那么其他人可能也会想。

如果其他人只是“也许”想专攻它太强了,如果有好的情况下,我们不希望他们,我们不应该使人们无法指定。 (有点类似于封装:您想访问一些数据,也许其他人也想访问-所以您将_this data_显式标记为public,而不是将所有数据默认为public。)

我担心,因为仅考虑此RFC已经给了我PTSD闪回...

但是,禁止使用此规范将如何阻止这些事情的发生?

如果在某些情况下我们不希望它们这样做,那么我们就不应该将其指定为不可能。

每当用户有合适的用例时,给用户提供功能并不是一个好主意。 如果它还使用户能够编写令人困惑的代码,则不会。

但是,禁止使用此规范将如何阻止这些事情的发生?

假设您看到foo.bar()并且想要查看bar()功能。 现在,如果您发现在匹配类型上实现的方法,并且未标记default知道它正在寻找的方法定义。 使用@withoutboats '提议将不再成立-相反,您将永远无法确定自己是否正在查看正在执行的代码。

取而代之的是,您将永远无法确定自己是否正在查看正在执行的代码。

这是允许将非默认impls本地化的效果的相当夸张。 如果您正在查看一个具体的隐含内容,那么您就知道自己正在寻找正确的隐含内容。 您可以访问此板条箱的全部资源; 您可以确定此隐含符号是否比“从不”更专业。

同时,即使使用default ,当未完成impl时,问题仍然存在。 如果正确的impl实际上是default impl,则您将面临同样的困难,即不确定这是否是正确的impl。 当然,如果采用专业化,情况将非常普遍(例如,今天对于几乎每个ToString印象都是如此)。

实际上,我确实认为这是一个相当严重的问题,但是我不相信default解决它。 我们需要的是更好的代码导航工具。 目前,当涉及特质展示时,rustdoc采取了非常“尽力而为”的方法-它没有链接到其来源,甚至没有列出毯子展示所提供的展示。

我并不是说这种改变无论如何都是is脚,但我认为它值得更细微的考虑。

每当用户有合适的用例时,给用户提供功能并不是一个好主意。 如果它还使用户能够编写令人困惑的代码,则不会。

确实,我绝对同意。 我想我在说的是另一个“用户”,它是您编写的板条箱的用户。 您不希望他们随意在板条箱中特化特质(可能以骇人的方式影响板条箱的行为)。 另一方面,我们将为您正在谈论的“用户”(即板条箱作者)提供更多权限,但是即使没有@withoutboats的建议,您也必须使用“默认”并遇到相同的问题。

我认为default在某种意义上是有帮助的,如果您想简化阅读代码,则可以要求没有人使用default或建立严格的文档规则来使用它。 在这一点上,你只需要大约忧default期从std ,这大概会乡亲们更好地理解。

我回想起可以对专业化用法强加文档规则的想法,这有助于获得专业化RFC的批准。

@withoutboats是我正确的阅读了您放松default动机,因为您想要default的受限形式,意思是“可重写,但仅在此板条箱中”(即pub(crate)但要default )? 但是,为了简单起见,您建议更改省略default的语义,而不是添加default -ness的刻度?

正确。 像default(crate)类的事情似乎有点过头了。

先验的,我想一个人可以通过箱子输出的东西来模拟这一点,不是吗? 在任何情况下,您不能简单地使用default方法引入私有助手特征并从您自己的最终impl调用它吗? 您要用户使用您的default而不提供自己的任何内容吗?

正确。 做类似default(crate)的事情似乎有点过分。

我不同意。 我真的想要受限形式的默认值。 我一直想提出这个建议。 我的动机是有时交集暗示等会迫使您添加默认值,但这并不意味着您要允许任意包装箱来更改您的行为。 抱歉,开会,我可以尝试列举一个例子。

@nikomatsakis我有相同的动机,我建议的是,我们只是删除了默认条件,即专门研究同一箱子,而不是添加更多杠杆。 :-)

如果偶然使用此非导出默认值可能是更常见的用法,则与#[macro_export]类似,将更容易记住#[default_export]功能。 一个中间选项可能是允许此导出功能用于pub usepub mod行。

使用pub关键字会更好,因为Macros 2.0将宏作为普通项目支持,并使用pub而不是#[macro_use] 。 使用pub表示全面可见性将是其一致性的一大胜利。

@withoutboats,无论如何,我认为有时候您会在本地进行专业研究,但不一定会为所有人

使用pub关键字会更好

具有pub default fn意思是“公开导出fn的默认值”,而不是影响函数本身的可见性,这会使新手感到超级困惑。

@jimmycuadra是您使用pub关键字的意思吗? 我同意@sgrif的说法,这似乎更令人困惑,并且如果我们将允许您明确地定义默认范围,那么我们为范围可视性决定的相同语法似乎是正确的路径。

就像你们俩都提到的那样,可能不完全是pub default fn 。 我只是说,让pub具有普遍意义是“将其他东西暴露给外部”。 可能有一些涉及pub的语法表述在外观上会有所不同,以免与将函数本身公开而混淆。

尽管有点语法,但我不会反对default(foo)pub(foo) -两者之间的对称性对我而言略微超出了语法的灵活性。

Bikeshed警告:我们是否考虑过将其称为overridable而不是default ? 它实际上是描述性的内容, overridable(foo)对我来说比default(foo) overridable(foo)读起来更好-后者建议“这是foo范围内的默认值,但其他可能是默认值在其他地方”,而前者说“这在foo的范围内是可以覆盖的”,这是正确的。

我认为前两个问题确实是:导出default ness是否更常见? 默认情况下应该导出default ness吗?

是的:您可以最大限度地提高与其他地方的出口的相似性,例如pub mod mymodule default;pub use mymodule::MyTrait default; ,或者也许与overridable相似。 如果需要,您可以只使用pub use MyModule::MyTrait::{methoda,methodb} default;某些方法导出default ness。

没有案例:您需要表达的是私有性,而不是公开性,无论如何它与Rust中的任何其他内容都大不相同,因此现在default(crate)成为控制这些出口的常规方法。

另外,如果导出和不导出default ness相当普遍,那么你们可能可以随意选择是或否,因此再次选择pub use MyModule::MyTrait::{methoda,methodb} default;可以很好地工作。

无论如何,所有这些符号看起来都是兼容的。 另一个选择可能是一些特殊的impl关闭了default ,但这听起来很复杂而且很奇怪。

@burdges那里是否有“是”和“没有”的标签,还是我误会了您的意思?

是的,哎呀! 固定!

我们有impl<T> Borrow<T> for T where T: ?Sized因此绑定Borrow<T>可以将拥有的值视为借来的值。

我想我们可以使用专业化来优化从Borrow<T>clone调用,是吗?

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界限,因为返回T时大概需要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

我对我们得出的所有结论都不是100%充满信心,尤其是自从Niko在事态令人振奋之后发表的评论以来。 有一阵子对我来说似乎是世界末日。

我确实很确定的一件事是,要求default不能与确保添加新的default impls始终向后兼容的保证兼容。 这是演示:

箱子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) { }
}

本地实现AC而不是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 { }

此impl已添加,并且是完全可专门化的impl,因此我们已经说过它是不间断的更改。 但是,它创建了传递的含义-我们已经有了“所有B的隐含C(不特殊化)”,通过添加“所有A的隐含B(特殊化)”,我们隐式添加了语句“所有A的隐含C(不特殊化)” ”。 现在,儿童箱无法升级。


可能的情况是,保证添加专门化的impls并不是一个重大更改,因为Aaron展示了(如您在上面的链接中所见),您可以编写impls来对默认性做出同等的保证, 。 但是,Niko在后来的评论中暗示,孤立规则可能禁止(或至少禁止)这种暗示。

因此,它的不确定性给我,如果“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: ReadT: BufRead并包含您要专门研究该特征的代码的部分来秘密完成此操作。 它甚至不需要在公共API中可见。

关于向后兼容性问题,我认为由于孤立规则,我们可以摆脱这些规则:

_ impl向后兼容,除非:_

  • _要实现的特征是自动特征。
  • _接收者是一个类型参数,并且impl中的每个特征以前都存在。

也就是说,我认为在所有有问题的示例中,添加的impl都是一揽子的impl。 我们想说完全默认的橡皮布标记也可以,但是我认为我们只能说添加现有橡皮布标记可能是一个重大变化。

问题是我们要对此做出什么保证-例如,我认为如果至少一揽子印象只能是基于包装箱中代码的重大更改,那将是一个非常不错的选择,因此您可以查看您的箱子里,就可以确定是否需要增加主要版本。

@withoutboats

关于向后兼容性问题,我认为由于孤立规则,我们可以摆脱这些规则:

_ impl向后兼容,除非:_

  • _要实现的特征是自动特征。
  • _接收者是一个类型参数,并且impl中的每个特征以前都存在。

也就是说,我认为在所有有问题的示例中,添加的impl都是一揽子的impl。 我们想说完全默认的橡皮布标记也可以,但是我认为我们只能说添加现有橡皮布标记可能是一个重大变化。

一周后,经过许多讨论,不幸的是事实并非如此

我们得到的结果是:crying_cat_face :,但是我认为我写的内容与您的结论相同。 无论如何,添加一揽子印象是一项重大变化。 但是只有一揽子暗示(和自动特质暗示); 据我所知,我们还没有发现非空白的impl可能破坏下游代码的情况(那将是非常糟糕的)。

我确实认为有一点我们可以放宽孤立规则,以便可以为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 :

也就是说,除非它包含新名称。 AFAIK包含新名称的impl永不中断。

@withoutboats我想知道我们是否可以/应该限制人们对自动特征的消极逻辑的依赖。 也就是说,如果我们说添加新的汽车特质符号是合法的重大更改,那么我们可能会警告可能由上游箱添加Send来破坏的符号。 如果我们有以下方法,这将是最好的方法:

  1. 稳定的专业化水平,可以通过在重要地点添加default来克服警告(大部分时间);
  2. 某种形式的显式否定隐含暗示,因此Rc类的类型可以声明其意图永远不会Send ,但随后我们有了针对自动特征的类型,因此可以将它们考虑在内。

我不知道我认为这取决于是否有强烈的动机。 在释放某个类型后,您似乎不太可能意识到它可能具有unsafe impl Send/Sync ; 我认为在大多数情况下都是安全的,您会事先写一个带有安全性的类型(因为这就是类型的重点)。

我一直在事实之后添加unsafe impl Send/Sync 。 有时是因为我使它成为线程安全的,有时是因为我意识到与我连接的C API可以在线程之间共享,有时是因为是否应该将Send / Sync当我介绍一种类型时,我不是在想什么。

在绑定C API时,我也会在事后添加它们-通常是因为有人明确要求这些限制,所以我接着检查了底层库的保证。

我不喜欢现在如何专门化关联特征,这种模式不起作用:

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有效:

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

impl Trait中的traits会释放出对这种模式的强烈渴望,但是我想知道是否存在一种更好的解决方案,其中通用隐式有效,但是专业化无效,因为它引入了类型错误?

@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编写此代码的方式令人困惑。 我认为代替使用impl BufRead作为Buffer的返回类型,您的意思是Self :: BufReader,对吗?

是的,我已将解决方案修改为基于impl特性的解决方案,然后又切换回去,但错过了特征中的返回类型。

也许像这种语言的类型系统之类的东西可能也很有趣,因为它看起来与Rusts类似,但是具有某些功能,可以解决当前的问题。
(当A是一个结构并实现特征B ,或者当A是一个特征以及对象的通用实现时, A <: B在Rust中为true我认为这个特征存在)

似乎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是特殊字符,还是因为没有为元组实现DisplayDefault是元组呢?

@shepmaster
我不知道它是否大约是Display ,但以下内容具有未为元组实现的Custom特征:

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不能是专业化的。
CC: Fn(Self) -> R更通用,因此第二个impl不能是专业化对象。
如果我错了,请告诉我。
但是我仍然不明白为什么它在Display的第一个示例中不起作用。

目前这是正确的行为。

Custom示例中,由于特殊的局部否定推理,这些脉冲不重叠。 因为特征是从这个箱子,我们可以推断, () ,不具有的IMPL Custom ,不重叠T: Custom 。 无需专业化。

但是,对于并非来自您的板条箱的特征,我们不会执行这种否定推理。 标准库可以在下一个版本中添加Display for () ,我们不希望这是一个重大更改。 我们希望图书馆具有进行此类更改的自由。 因此,即使()不表示Display,我们也不能在重叠检查中使用该信息。

而且,由于()并不表示Display,因此它的含义比T: Display更具体。 这就是为什么专业化不起作用的原因,而在默认情况下, (): Default ,因此impl比T: Default更具体。

像这样的隐含符号属于“ limbo”,我们无法假设它重叠或不重叠。 我们正在尝试找到一种原理性方法来完成这项工作,但这不是专业化的第一个实现,它是对该功能的向后兼容扩展,将在以后推出。

我提交了#40582来跟踪与寿命相关的健全性问题。

我在尝试使用专业化时遇到问题,我认为它与@antoyo不太一样,我将其作为单独的问题提交了#41140,如有必要,我可以将其中的示例代码放入此处

@ afonso360不,一个单独的问题就可以了。

总体而言: Chalk的工作目前尚无法进行专门化

有人可以弄清楚这是错误还是故意禁止的错误? https://is.gd/pBvefi

@sgrif我认为这里的问题只是不允许默认关联类型的投影。 诊断可能会更好: https

您能详细说明为什么它会被禁止吗? 我们知道,无法添加更多具体的隐式内容,因为它会违反孤立规则。

该注释表明在某些情况下有必要要求健全(尽管我不知道为什么),而在其他情况下则必须强制接口的使用者将其视为抽象类型: https :

有谁能看过https://github.com/rust-lang/rust/issues/31844#issuecomment -266221638吗? 就我所知,这些冲动应具有专业性才有效。 我相信有一个阻止它们的错误。

@sgrif我相信您的代码存在的问题可能类似于https://github.com/rust-lang/rust/issues/31844#issuecomment -284235369中的问题,其中@withoutboatshttps://github.com中进行了解释@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 ,其中包括T where T: AT 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 implgenerate不进行类型检查。

链接到这里的线程

@ burns47这里有一个令人困惑但有用的解决方法: https :

@dtolnay不太令人满意-如果我们专门研究我们不拥有(且无法修改)的特征,该怎么办? 我们不需要重写/重构特征定义来执行此IMO。

任何人都可以评论以下问题中的代码是否被有意拒绝吗? 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并自动实现EqPartialEqPartialOrd

请注意,实现Ord并同时导出PartialEqPartialOrd是危险的,并且可能导致非常细小的错误! 有了这些默认的隐含符号,您就不容易得出这些特征,因此可以在某种程度上缓解该问题。


另外,我们修改推导以利用专业化优势。 例如,在#[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则使用默认的impl。 但是,如果是,则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。

以防万一没人看到它,这篇博客文章提出了一个建议,以面对基于生命周期的调度时发出专业化声音。

由于复制关闭已在Beta中稳定下来,因此开发人员现在有更多的动力来稳定专业化。 原因是FnFnOnce + Clone表示两个重叠的闭包,在许多情况下,我们需要为它们两个实现特征。

只需弄清楚rfc 2132的措辞似乎暗示着只有5种类型的闭包:

  • FnOncemove闭包,所有捕获的变量都不是CopyClone
  • FnOnce + Clonemove闭包,所有捕获的变量均为Clone
  • FnOnce + Copy + Clonemove闭包,所有捕获的变量为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`

@glandium那里到底发生了什么? 很好的例子,这里是游乐场链接: https :

是吗? 在我的示例中没有空的印象。

g

 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事实证明,声音是_hard_。 我相信目前正在开展一些有关“始终适用的隐含”想法的工作,因此有一定进展。 参见https://github.com/rust-lang/rust/pull/49624。 另外,我相信粉笔工作小组也在努力实现“始终适用的隐含”想法,但是我不知道这已经走了多远。

经过一番争吵之后,似乎可以通过使用specializationoverlapping_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如果您默认使用fn而不是impl,则您的用例可以正常工作:

#![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/

我的猜测是每个人现在都专注于该版本...

好的,谢谢……似乎这个问题一直持续下去,但是还算公平。 我知道这很难。 不幸的是,现在注意力转移到了其他地方。

有人可以更具体地解释在完全单态情况下不允许默认关联类型的投影背后的原理吗? 我有一个用例,我想使用该功能(特别是,使用非完全单态的类型来调用该特征在语义上是不正确的),如果没有健全性问题,我将不完全理解为什么这是不允许的。

嗯,我知道,事实证明投影与一般专业之间的相互作用很差

不幸的是,我不确定如果没有这样的功能,是否真的有办法做我想做的事情:当两个传入的实现特定特征的类型在语法上相等时,我希望有一个关联类型输出“ True”,和“ False”(不正确)(“ False”案例会触发更昂贵的特征搜索,可以决定它们是否在“语义上”相等)。 在我看来,唯一真正的选择是始终进行昂贵的搜索。 从理论上讲这很好,但价格可能会高很多

(如果打算关闭特征,我可以解决这个问题,方法是只枚举头对位置的每个可能的构造函数对,并让它们输出True或False;但是它打算对存储库外部的扩展开放,以便可以可能无法工作,尤其是因为两个不同用户存储库中的实现不一定彼此了解)。

无论如何,也许这只是表明我想做的事情不适合特征系统,我应该切换到其他机制,例如宏:P

确实,我想要的是“否定性推理”的味道(尽管封闭的特质并不足够)。

否定性推理的替代方法是,一个类型仅实现一组封闭特征集中的一个特征,以使该集合中具有其他特征的实现不能重叠(例如, T实现{ Float | Int | Bool | Ptr } )。

即使有一种方法可以在Rust中强制执行(没有,AFIAK吗?),我也不认为这可以解决我的问题。 我希望不同板条箱中的用户能够实现任意数量的新常量,这些新常量应该只与自己比较,而与其他所有已定义常量(包括在板条箱定义时未知的常量)不相等。 我看不到任何封闭的特征集(或什至是特征族集)如何单独实现该目标:这是一个根本无法直接查看类型无法解决的问题。 可以在默认投影下使用的原因是,您可以将所有内容默认为“不比较相等”,然后在定义常量的任何条板箱中实现新常量与其自身的相等,而不会与孤儿发生冲突规则,因为特质实现中的所有类型都在同一个箱子中。 如果我想要除平等之外的几乎任何这样的规则,即使这样也行不通,但是平等对我来说已经足够了:)

目前,这是可行的:

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 ()与您的默认impl冲突。

@Boscop在这种情况下,无论如何,默认的impl应该被下面的非默认值覆盖。 例如,如果我在其他提示之前添加了impl Bar<bool> for () {} ,那么我希望它可以正常工作(按照RFC /期望)。 那不是吗?

沿着您提到的反例深入研究,我意识到(或相信)该例满足“始终适用”的测试,并且可能正在研究中。

此问题可能取决于#45814。

是否有计划支持专业化中不存在的默认特征界限?

作为一个非常有用的示例,您可以通过创建带有任意Inner的通用Struct来创建不共享的功能,从而轻松地组成不同类型的处理。

#![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 impl(以及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>特殊的(避免使用单词“ special”)? 即使将来暗示std::error::Errorimpl 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,有一节专门介绍内在的隐含符号。 有人给了我尝试吗?

至少对于固有的const方法,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建议将方法提升为特征,为类型实现该特征,并专门实现impl。 我想对于const fn方法,该类型特征的那些impls必须是const impls。

对于碰到这种情况并对状态感到好奇的任何人-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上个月写道(例如,在另一种情况下;大胆的我的):

daccess-ods.un.org daccess-ods.un.org [专业化]中有一个关键问题从未得到令人满意的解决,一个关于生命周期和特质的技术稳健性关注[...],然后,[上面两个链接]。 这些想法似乎已经基本解决了问题,但与此同时我们一直很忙,没有时间跟进。

尽管显然仍有工作要做,但听起来还是充满希望。

(发布此消息是因为几周前我发现了该线程,并且不了解去年的进展,然后最近偶然发现了这些帖子。上面提到了一些评论,但是GitHub使得除了第一个和第一个之外,其他任何一个都很难看到。在长线程上的最后几点评论:cry:。如果此更新将它添加到问题描述中,可能会有所帮助。)

大家好! 有人可以告诉我为什么这个用例不起作用吗? 错误或预期行为?

作为这个例子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

@Hexileedefault放在方法上而不是impl上。

@KrishnaSannasi示例2

@zserik是的,我知道。 我认为它尚未实施,或者已被删除。 无论如何,它现在不起作用。

现在显然不起作用,但是我认为它应该起作用。

我在这里问这个问题,是因为我没有注意到这个话题在其他地方出现-是否有任何计划来default修改各种标准库函数,类似于我们拥有const -可以安全地使用功能吗? 我要问的主要原因是默认的通用FromInto实现( impl<T, U: From<T>> Into<U> for Timpl<T> From<T> for T )使编写全面的通用From变得很困难Into下游core ,如果我可以在自己的包装箱中覆盖这些转化,那就太好了。

即使我们允许From / Into特殊化,由于晶格问题,它对通用impls也无济于事。

@KrishnaSannasi我不这么认为。 例如,如果FromInto是可特殊化的,则此代码应该可以工作,但不能,因为它们不是:

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,
}

如果将FromInto转换为不具有这些通用实现的自定义特征,则该方法有效: https :

@Osspial好吧,如果您尝试使用默认的impl进行仿真,则会看到此问题,

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

我会重复一遍,将From/Into impl更改为标准库中的默认impl不会使Into通用impls成为可能。 (并且不会影响From通用展示次数)

嗨,当前的专业化实现中存在一个严重的错误。 我将其标记为错误,因为即使这是一个明确的设计决定,它也会阻止我们使用最强大的专业化功能之一,即可能创建“不透明类型”(这不是正式名称)。 这种模式是其他语言中提供类型类(例如Haskell或Scala)的最原始的构建块之一。

这种模式很简单–我们可以定义WithLabelWithID结构,这些结构将一些字段和方法添加到基础结构中,因此例如,如果我们创建WithLabel<WithID<MyType>>则可以获得idlabel以及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:

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个不同的不透明类型定义,它们添加了不同的字段,例如WithIDWithLabelWithCallback ,...命名。 使用当前的专业化行为,我们将需要定义... 1000多种不同的特征实现! 如果注释掉的代码将被接受,我们将只需要10个trait实现,实现每个新类型将只需要一个附加实现

我不确定您的代码与专业化之间的关系。 如果删除了第一个#![feature(specialization)]行,则您的论点(您的初始代码可以编译,但是注释了的test(v1);行如果没有您提供的手动显示就不会编译)。

@qnighy该代码应在取消对impls HasLabel for THasID for T注释后进行编译-它们正在使用特殊化。 目前,它们已被拒绝(尝试在我提供的代码中取消注释!)。 现在对您有意义吗? 🙂

让我们考虑三个实例WithLabel<WithID<A>>WithID<WithLabel<A>>WithLabel<WithLabel<A>> 。 然后

  • 第一个展示包含WithLabel<WithID<A>>WithLabel<WithLabel<A>>
  • 第二个印象包括WithID<WithLabel<A>>WithLabel<WithLabel<A>>

因此,这对impls不满足RFC中的以下条款:

为了确保专业化是连贯的,我们将确保对于重叠的两个冲刺IJ ,我们有I < JJ < I 。 也就是说,一个必须比另一个真正更具体。

它是在你的情况下,真正的问题太多,因为HasLabel的IMPL WithLabel<WithLabel<A>>可以通过两种方式来解释。

RFC中已经

晶格规则所解决的局限性仅次于专业化的主要目标(如《动机》中所述),因此,由于可以稍后添加晶格规则,因此RFC目前仍坚持使用简单链规则。

@qnighy ,感谢您的考虑。

在您的情况下,这也是一个真正的问题,因为可以用两种方式解释WithLabel<WithLabel<A>>的HasLabel隐式表示。

如果我们不考虑这是真实的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来允许重叠的非标记特征,但是这种hack需要另外三个特征(一个用于标记特征,两个用于通过专门化来重新获取擦除的数据)。

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

@qnighy我针对此错误创建了一个单独的问题: https :

好的,我只是发现auto traits永远不会在这里解决,因为(根据https://doc.rust-lang.org/nightly/unstable-book/language-features/optin-builtin-traits。 html)传播到结构中的所有字段:

自动特征(如标准库中的“发送”或“同步”)是针对每种类型自动实现的标记特征,除非该类型或其所包含的类型已通过负隐式显式选择退出。

编辑
@qnighy不知何故我忽略了您提供的游乐场链接。 ❤️非常感谢你。 它的工作原理,让我惊讶的是这种解决方案多么棘手。 我们现在能够表达这一点令人惊讶,希望这种可能性将来不会消失!

在这种情况下, overlapping marker traits是我们现在可以使用的唯一hack,但是我认为将来允许使用某种更简单的解决方案来表达不透明类型(如我以前的文章中所述:https) ://github.com/rust-lang/rust/issues/31844#issuecomment-549023367)。

一个非常简单的示例(上述示例的简化)失败了:

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

我不相信这会遇到用格规则确定的问题,但是也许过于简单的求解器认为可以吗?

没有这个,当前的实现对我的目的是无用的。 如果允许上述操作,那么我相信也有可能在包装器类型上实现Into )。

对于尚不知道的任何物体: dtolnay发现了一个很棒的技巧,可以在稳定的防锈上使用(非常有限的)专业化技术

我不确定这是否已解决,但是必须重新定义其方法的默认实现的特征,以便可以将其标记为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的行为有所不同,只是为了使自己放心。

也许您是在说这两个变体是等效的,只是一个变体更快,因此您不确定如何测试要获得哪个版本? 我同意那种情况,我不知道您现在如何测试。

您可以使用宏进行测试。 我对Rust有点生疏,但有些事情...

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

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

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

然后在专门的impl中使用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我同意这似乎是错误的。

我对程序有疑问:这个问题有多重要?人们试用此功能有多重要? 据我了解,当前的实现尚不完善且不完整,可能会被粉笔或其他东西完全取代。 如果是这样,我们是否应该取消实施此功能和其他功能(例如GAT),直到可以正确重做?

请不要取消实现。 破碎,不健全和不完整仍允许进行实验。

如果是这样,我们是否应该取消实施此功能和其他功能(例如GAT),直到可以正确重做?

请不要,PyO3(Python绑定库)当前依赖于专业化。 参见https://github.com/PyO3/pyo3/issues/210

std的合理金额也不也取决于它吗? 尽管我记得有很多关于矢量和字符串相关内容的专门内部实现。 并不是那应该防止取消实现,只是它不会像从类型检查器中删除相关部分那样简单。

@Lucretiel是的,许多有用的优化(尤其是围绕迭代器的优化)都依赖于专业化,因此取消实现将是巨大的性能回归。

例如,如果没有专门化, FusedIteratorTrustedLen是没有用的。

PyO3(Python绑定库)当前依赖于专业化

由于“不健全”的部分,这很可怕。 由于使用专业化错误,标准库存在严重的健全性错误。 您如何确定自己没有相同的错误? 尝试改用min_specialization ,希望它至少可以减少声音。

也许specialization应该收到类似于const_generics的警告,说“此功能不完整,不健全和损坏,请勿在生产中使用”。

许多有用的优化(尤其是围绕迭代器的优化)依赖于专业化,因此取消实现将是巨大的性能回归。

如今,它们依靠min_specialization (请参见例如https://github.com/rust-lang/rust/pull/71321),该漏洞具有最大的健全性漏洞。

@nikomatsakis

我同意这似乎是错误的。

知道预期的代码是什么吗? 我首先想到default impl还要设置type Output = Self; ,但是实际上在提议的RFC中Output = T

@RalfJung是否有可能记录min_specialization ? 我觉得在板条箱上使用完全未记录的功能比已知(或可能未知)健全性错误的功能更具风险。 两者都不是好事,但至少后者不只是编译器内部。

在#71321 PR以外的跟踪问题中,我找不到min_specialization提法-根据Unstable的书,这就是该功能的跟踪问题。

我对该功能也不了解,只是看到了libstd健全性修复程序。 它在https://github.com/rust-lang/rust/pull/68970中引入,它解释了有关它的更多信息。

@matthewjasper是否有必要对此进行更多记录并要求每晚feature(specialization)进行迁移?

似乎至少应该有一个警告。 似乎该功能已被公然破坏并且在当前状态下使用很危险。

我认为specialization可以成为min_specialization的代名词,但是如果现有项目(例如PyO3或其他)需要的话,可以添加另一个unsound_specialization功能。 这样可以节省只使用min_specialization任何人,但是其他人会收到错误消息,并且可以在此处查找新名称。

@RalfJung

知道预期的代码是什么吗?

好吧,在某个时候,我们一直在考虑一种默认可以相互依赖的模式。 因此,我想到那时下面的方法会起作用:

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任何成员,那么您就必须覆盖它们的全部。 后来我们放弃了这个想法,然后进行了各种迭代,例如“默认组”(在这里也可以使用),最终没有采用任何解决方案,因为我们认为一旦处理完就可以稍后解决。另一个是紧迫的问题(cc#71420)。

请不要,PyO3(Python绑定库)当前依赖于专业化。 参见PyO3 / pyo3#210

这里是PyO3维护者-我们赞成放弃专业化,以便我们可以使用稳定的Rust。 min_specialization在其他专业化完成之前是否可能会稳定下来?

我认为在2021年计划lang设计会议中有人试图稳定min_specialization(在youtube上;对不起,我在电话上,或者我会尝试找到一个链接)。 我忘了他们怎么说

我认为在2021年计划lang设计会议中有人试图稳定min_specialization(在youtube上;对不起,我在电话上,或者我会尝试找到一个链接)。 我忘了他们怎么说

我认为这是正确的YouTube链接: https :
(也在我的手机上)

是的,就是这样。 这是特定讨论的链接: https :

我在开发的实验库中一直使用#[min_specialization] ,所以我想我会分享自己的经验。 目标是以最简单的形式使用专业化:拥有一些较窄的情况,并且比一般情况具有更快的实现。 特别是,要使密码算法通常在恒定时间内运行,但是如果所有输入都标记为Public ,则具有运行在可变时间内更快的专用版本(因为如果它们是公开的,我们就不会关心通过执行时间泄漏有关它们的信息)。 另外,根据是否对椭圆曲线点进行了归一化,某些算法会更快。 为了使它起作用,我们从

#![feature(rustc_attrs, min_specialization)]

然后,如果需要按照最大最小专业化中的说明制作_specialization predicate_特征,则可以用#[rustc_specialization_trait]标记特征声明。

我所有的专业化都在此文件中完成,的示例

该功能可以正常工作并满足我的需求。 显然,这是使用锈迹斑斑的内部标记,因此很容易在没有警告的情况下破裂。

反馈的唯一负面影响是我认为default关键字没有意义。 从本质上说, default现在的意思是:“此impl是可特殊化的,因此将包含此一子集的impls解释为它的特殊化,而不是冲突的impl”。 问题是它导致看起来很奇怪的代码:

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

在这里,第二个impl专用于第一个,但它也是defaultdefault的含义似乎丢失了。 如果您看一下其余的impls,很难找出哪个impls专门研究哪个。 此外,当我做出与现有的重叠的错误提示时,通常很难弄清楚我哪里出了错。

在我看来,如果一切都可以专门化,并且当您对某项进行专门化时,则可以精确地声明要专门化的暗示,这将变得更加简单。 将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] 。 AS-是,有一个rustc_unsafe_specialization_marker像特质属性FusedIterator提供优化提示,让他们可以专门上。 @matthewjasper写道:

这是不合理的,但我们允许它在短期内使用,因为它在释放纯安全代码后不会像专门研究traits方法一样导致使用。

我假设计划是实施@aturon的建议,并为诸如( where specialize(T: FusedIterator) )这样的特征添加一种特殊化方式。 但是目前看来,任何代码都可以专注于这些特性。 如果按原样稳定下来,人们可以编写依赖它的稳定专业知识,这意味着这种不稳固性将得到稳定。

那么,这些特征的专业化也应该限于标准库吗? 标准库是否可以通过专注于它们而获得足够的收益?

如果按原样稳定下来,人们可以编写依赖它的稳定专业知识,这意味着这种不稳固性将得到稳定。

我的理解是, min_specialization不能原样用于稳定。

我想第二点在专门的impls上使用某种标记。 在rustc中有很多情况的代码和标准库没有按照它的样子做,因为没有办法知道专业化实际上正在发生:

Copy的不必要的专业化:
https://github.com/rust-lang/rust/pull/72707/files#diff -3afa644e1d09503658d661130df65f59L1955

不是“专业化”的:
https://github.com/rust-lang/rust/pull/71321/files#diff -da456bd3af6d94a9693e625ff7303113L1589

除非通过传递标志覆盖默认的impl,否则由宏生成的实现:
https://github.com/rust-lang/rust/pull/73851/files?file-filters%5B%5D=#diff -ebb36dd2ac01b28a3fff54a1382527ddR124

@matthewjasper ,最后一个链接似乎没有链接到任何特定的片段。

我不确定这是否是一个明确的目标,但是AIUI没有标记专门的impls的事实为您提供了一种避免破坏Blank Impls更改的方法。 一个新的default impl<T> Trait for T与下游符号没有冲突-那些只是专业化的。

只是未标记就可以成为警告吗?

在rustc中有相当多的代码案例,而标准库却没有按照它的样子工作,因为无法知道专业化实际上正在发生

我在Java方面的经验是相似的(尽管不完全相似)。 很难找到一个类的哪个子类实际上正在运行...

我们也希望在专门化的impls上有一些标记,也为了阅读时的清晰,对吗?

我们可以将标记放在两个地方,这样可以改善rustc错误或警告消息,因为它们现在知道是否需要专门化,并且可以指向另一个地方(如果存在)。

如果上游板条箱添加了一个impl,则除了简单升级外,下游板条箱还可以采用一些技巧来允许针对新旧版本进行编译,但不确定这样做是否有益。

我认为差异可能太大而无法显示更改。 它指向此: https :

回复:毯子暗示,他们无论如何都在打破变革:

  • 它们可能会与下游展示广告部分重叠,这是不允许的
  • 连贯性可以以更微妙的方式假定它们不存在(这就是为什么在内部添加储备暗示的原因)
  • 专业化提示必须始终适用,这意味着:

    • 我们打破了人们的冲动( min_specialization做什么)。

    • 我们要求他们以某种方式注释其特质界限,以使其在必要时始终适用。

    • 我们隐式地对其进行始终适用的更改,并在现在应用默认impl时潜在地引入了细微的运行时错误。

@cuviper实际上,我觉得即使有专门知识,在添加新的毯子impls方面仍然存在一些边缘情况。 我记得我曾经试图弄清楚允许我们添加impl<T: Copy> Clone for T { } imp的情况,无论如何我写了这篇关于它的博客文章...但是我现在不记得我的结论是什么。

无论如何,我们可以把它作为警告,不要带有#[override]注释。

就是说,如果我们可以让用户声明他们正在专门研究哪些暗示(不知道我们将如何做),那么它将简化一些事情。 现在,编译器必须推断出impls之间的关系,这总是有些棘手。

我们在粉笔项目中要做的未决项目之一是尝试回去并阐明应该如何在其中表达专业化。

在rustc中有相当多的代码案例,而标准库却没有按照它的样子工作,因为无法知道专业化实际上正在发生

我在Java方面的经验是相似的(尽管不完全相似)。 很难找到一个类的哪个子类实际上正在运行...

早在五月,我就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"
}

此公式为上游提供了更多控制权,以选择适用的impls的顺序,并且由于这是特征/功能签名的一部分,因此将出现在文档中。 由于解析顺序是明确的,因此,IMO阻止了“追逐”来知道哪个分支实际适用。

这可能会使生命周期和类型相等性周围的错误更加明显,因为在实现专业化时只有上游可以满足它们(因为下游仅实现了“专业化特征”。

该表述的缺点是,它与RFC中的表述截然不同,并且自2016年开始实施,并且该线程上的至少一些人对此表示担忧,认为它不会像当前的那样具有表现力和/或直观性。专业化功能(我发现“类型匹配”非常直观,但是在提出该建议时我有偏见)。

匹配语法可能具有另一个(语法上的)好处:如果将来在某个时候使用const评估的匹配卫士进行扩展,则无需进行类型体操来表达以const表达式为条件的边界。 例如,可以应用基于size_ofalign_ofneeds_drop或数组大小的专业化。

@dureuill感谢您的信息! 这确实是一个有趣的想法。 我关心的一个问题是,它不一定能解决其他一些专门化的预期用例,特别是@aturon本博客文章中描述的“渐增行为”用例。 尽管如此,值得牢记。

@dureuill这个想法确实很有趣并且可能具有很大的潜力,但是替代方法并不总是等同的交换。
我不认为这是因为没有机会完全取代更一般的实现。 另外一个问题可能是,我们实际上并不支持您的建议所依赖的where语法RFC中存在的所有功能。
该建议很有趣,因此也许可以将自己的RFC作为单独的功能,而不是专业化的竞争对手,因为这两个都是有用的,而且我看不出它们为什么不能在一起生活。

@ the8472 @nikomatsakis,@暗军团:感谢您的积极反馈! 我想在IRLO线程中回答您的一些评论,因为我不想在跟踪问题上太吵(对每个希望获得专业化新闻并发现我的漫漫:flushed:的人感到抱歉)。

如果我设法编写可发布的内容,则可以打开一个单独的RFC。 同时,我很乐意就链接的

我也赞成在专门的impls上使用某种标记。

2021版的方法使我们可以保留更多关键字(例如specialize )。 考虑到此功能的复杂性和历史,我认为它不会在2021版发布之前稳定下来(请随时证明我是错误的),这意味着-我认为-正在使用一个或多个新关键字)是合理的。

否则,似乎仅存在的.....适合用作标记的super

通过重用来自https://github.com/rust-lang/rust/issues/31844#issuecomment -639977601的@LLFourn示例进行总结:

  • 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>
  • specspecialize缩写,如implimplement )( @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>

已经保留的关键字可以在这里找到

specspecialize缩写,例如implimplement

人们已经很熟悉“ spec”作为“ specification”(例如“ HTML 5 spec”)的简写,因此我认为它不是“ specialize”的好简写。

override是一个保留关键字,我认为它是针对函数的,因此它可能对impl块可用。

specialize也取决于语言环境-作为澳大利亚人,它对我来说是专业化的,因此使用“ spec”消除了语言环境的歧义。

specialize也取决于语言环境-作为澳大利亚人,它对我来说是专业化的,因此使用“ spec”消除了语言环境的歧义。

可以正常工作,只是'spec'是规范的常见缩写,因此我认为使用'spec'表示专业化会造成混淆。 即使在澳大利亚,单词的拼写有所不同,但是如果每个单词的拼写为“ z”或“ s”,则每个人仍然可以理解该单词的意图。

作为加拿大人,我不得不说专业化/专业化并不是编程中使用的唯一一个随地区而异的词。

在这里,我们使用“颜色”,但是在很少的情况下,当编程语言或库使用“颜色”而不是“颜色”时,它总是使我震惊。 不管是好是坏,美式英语在API设计中都是事实上的标准,而对于像颜色/颜色这样的单词,不可避免地选择使用一种拼写而不是另一种拼写。

考虑到我对“规范”意味着“规范”的强烈期望,我认为这是另一种情况,我们应该考虑将美国英语拼写作为最差的选择。

可能是事实上的事实,但这并不意味着可以使用它们。 我发现自己正在进行诸如“将颜色用作颜色”之类的导入。 我也总是在s vs z上绊倒。 我认为,考虑到Rust对包容性和可访问性的积极态度,选择不依赖于语言环境的语言术语是有意义的,因为诸如颜色/颜色和s / z这样的小用户挫折感会逐渐增加。

我原则上同意。 对于这种情况,我只是持怀疑态度,因为有一个与语言环境无关的选择,不会引起比解决的问题更多的问题。

作为非英语母语人士,我觉得说英语母语人士会抱怨额外的u作为包容性的障碍感到有些可笑。 试想一下,如果所有内容的拼写都不奇怪,而是用完全不同的语言编写,将会是什么样。

换句话说:Rust中使用的每个术语都取决于语言环境。

不管是好是坏,Rust中的内容都是用美国英语拼写的。 对于许多人来说,这意味着要使用第二种或第三种语言。 对于其他人,则意味着必须稍微调整拼写。 这就是使一群人一起工作所需要的。 我认为尝试选择在许多英语变体中拼写相同的单词所带来的好处与选择一个明确的术语相比是微不足道的-而且如上所述, spec是模棱两可的。

使用special作为关键字?

或者,输入两个关键字: specializespecialise并使它们相等...

(或者,您有趣的非美国人可以学习拼写正确的专有名词:我们:😂)

我不能说大多数语言的功能,但是CSS在所有新内容中都使用了美国英语拼写。 有趣的是,美国英语似乎在编程中也被更频繁地使用。

@ mark-im不幸的是,这是一个滑坡,导致人们争论,Rust应该在学习者可能来自的每种主要语言中都有一组替代关键字。

这也不必要地使语言具有多个关键字同义词,因为人们和解析器仅习惯于空白可以像这样变化的想法。

(更不用说它可能会引起对库中同义同义词的推动,这将需要rustdoc设计和实现工作,以防止它们成为负面对象。)

而不是争论我们希望该标识符位于哪种英语方言中,也许我们可以妥协并将其放入希伯来语中

@ssokolow虽然滑坡参数通常不适合使用,但在这种情况下,我确实同意。 一个人可能会说多种语言很好,但是至少有两个原因导致它不是:

  • 不同语言中的某些单词看起来相同但含义不同(目前无法提供与编程相关的示例,但有一个随机示例:斯洛伐克语中的a在英语中为and
  • 即使他们知道另一种语言,人们很难阅读另一种语言的代码。 我从经验中知道。 (长话短说:在大学教育骗局中,我很难理解某些文本,这些文本直接从英语翻译成我的“母语”。)

现在倒退,为什么不应该使用其他英语方言,而不是其他语言? 我没意思。 一致性(一切都是美国英语)似乎最简单,最容易理解且最不容易出错。

话虽这么说,我会很高兴“你是说XXX吗?” 错误消息的方法。 没有其他问题的中性词也可以。

至少没有人需要用代码讨论足球。 ;)

大约70%以英语为母语的人居住在使用美国拼写的国家/地区。

也..

“ -ize的拼写经常被错误地视为英国的美国主义。自15世纪以来,-ize的使用早于-ise。 ise是通过法语-iser来的。牛津英语词典(OED)推荐-ize并列出-ise形式作为替代。”

“牛津大学出版社(OUP)的出版物(例如,亨利·沃森·福勒的《现代英语用法词典》,《哈特规则》和《牛津英语用法指南》也建议-ize。但是,罗伯特·艾伦的《袖珍·福勒的现代英语用法》考虑了拼写在美国以外的任何地方都可以接受。”

参考 https://zh.wikipedia.org/wiki/American_and_British_English_spelling_differences# -ise,_- ize _(-isation,_- ization)

看来西班牙文和意大利文有1或2个z,所以不确定法国人从哪里来,也许是德国人呢?

能否将围绕特定关键字和命名的其他操作转移到内部线程中? 我正在关注此问题,以获取有关该功能的进度更新,并且此讨论开始变得有些嘈杂。

此页面是否有帮助?
0 / 5 - 0 等级