Skip to content

Tracking Issue for auto traits (auto_traits) -- formerly called opt-in built-in traits (optin_builtin_traits) #13231

Open
@flaper87

Description

@flaper87
Contributor

This is the tracking issue for RFC 19.

Checklist

Here is a check-list of code to write and tricky scenarios to be sure we handle:

Activity

self-assigned this
on Mar 31, 2014
changed the title [-]opt-in built-in trait bounds RFC tracker[/-] [+]opt-in built-in bounds traits RFC tracker[/+] on Mar 31, 2014
pnkfelix

pnkfelix commented on Apr 3, 2014

@pnkfelix
Member

Marking 1.0, P-backcompat-lang.

added this to the 1.0 milestone on Apr 3, 2014
nikomatsakis

nikomatsakis commented on Apr 15, 2014

@nikomatsakis
Contributor

cc me

flaper87

flaper87 commented on Apr 22, 2014

@flaper87
ContributorAuthor

I'm actively working on this. Unfortunately, I've moved quite slowly because of other bugs I had to fix. I hope to be able to submit a PR this week.

303 remaining items

madsmtm

madsmtm commented on Sep 27, 2023

@madsmtm
Contributor

Opting out of auto traits related to systems you’re not actively integrating with feels like a wrong thing to do in almost any situation.

In my case too, you'd only need to opt out of AutoreleaseSafe when you're doing stuff with Objective-C (or perhaps Swift, but they're close enough) autorelease pools.

I'll add that if a library wanted to make sure to opt out of all auto traits (e.g. if it implemented some sort of dynamic type), even for generic structs, they could do:

trait HelperTrait {}

struct MyTypeThatOptsOutOfEverything<T> {
    inner: T,
    p: PhantomData<dyn HelperTrait>,
}

Of course this is yet another SemVer footgun to be aware of, but fortunately we have tools for that now.

GoldsteinE

GoldsteinE commented on Sep 27, 2023

@GoldsteinE
Contributor

Oops, I mixed up “opting out” and “opting in” in my original message. Either way, the point stands: I don’t see a reason to opt out of arbitrary auto traits unless you’re doing something that can affect this particular system. Author of the auto trait guarantees that it’s safe to impl it if all the fields impl it.

steffahn

steffahn commented on Dec 11, 2023

@steffahn
Member

For Ungil, I’ve found the issue

which is that an auto-trait is unable to restrict access to certain types even if those types always feature a non-'static lifetime as provided by to a higher-kinded for<'a> FnOnce(SomeType<'a>)-style callback.

I’m relatively certain that the AutoreleaseSafe example suffers from the same issue.

Of course one approach would be to declare scoped-tls unsound on the (arguably quite thin) grounds that the standard library only sets precedent that 'static data is allowed to be shared via thread-local storage. Assuming we don’t declare scoped-tls unsound, an auto traits feature seems entirely unable to be a solution for sound API in either of the API cases for AutoreleaseSafe nor Ungil.

GoldsteinE

GoldsteinE commented on Dec 11, 2023

@GoldsteinE
Contributor

Oh, that’s really sad. If scoped-tls is valid (and I don’t see a reason for it not to be), then auto trait Ungil can only make pyo3 less-obviously-unsound instead of sound. IMHO that’s still a valid reason for it to be an auto trait, but it’s really unfortunate that it doesn’t just make it clean and sound.

madsmtm

madsmtm commented on Dec 11, 2023

@madsmtm
Contributor

Hmm, I don't think that's a problem for objc2's AutoreleaseSafe, since the only type it's used on is AutoreleasePool<'pool>, whose only purpose is to carry a lifetime (it's a ZST that doesn't actually carry any data), which it seems like scoped-tls-hkt manages to properly bind to the execution of the closure.

So even though we can access the pool, we can't actually do anything bad with it, since it's only the lifetime that we care about, and that is still correctly bounded. See the following example for details.

```cargo
[dependencies]
# requires `auto_traits` feature
objc2 = { version = "0.5", features = ["unstable-autoreleasesafe"] }
scoped-tls-hkt = "0.1"
```

use objc2::rc::{autoreleasepool, AutoreleasePool, Id};
use objc2::runtime::NSObject;
use scoped_tls_hkt::scoped_thread_local;

fn main() {
    autoreleasepool(|pool1| {
        scoped_thread_local!(static POOL: for<'pool> AutoreleasePool<'pool>);

        POOL.set(pool1, || {
            let obj = autoreleasepool(|_pool2| {
                POOL.with(|pool1| {
                    Id::autorelease(NSObject::new(), pool1)
                })
            });

            // If we manage to get here, then that's unsound, since the object
            // would have been released in the second autorelease pool.

            println!("{obj:?}");
        });
    });
}

If you manage to produce a similar example that does compile, then I'd love to know!

steffahn

steffahn commented on Dec 11, 2023

@steffahn
Member

@madsmtm Here we go:

use std::cell::Cell;

use objc2::rc::{autoreleasepool, AutoreleasePool, Id};
use objc2::runtime::NSObject;
use scoped_tls_hkt::scoped_thread_local;

struct PoolHolder<'p> {
    pool1: AutoreleasePool<'p>,
    obj_cell: &'p Cell<Option<&'p NSObject>>,
}
trait IPoolHolder {
    fn autorelease(&self, id: Id<NSObject>);
}
impl<'pool> IPoolHolder for PoolHolder<'pool> {
    fn autorelease(&self, id: Id<NSObject>) {
        self.obj_cell.set(Some(Id::autorelease(id, self.pool1)));
    }
}

fn main() {
    autoreleasepool(|pool1| {
        scoped_thread_local!(static POOL_HOLDER: for<'p> &'p dyn IPoolHolder);

        let obj_cell = &Cell::new(None);

        POOL_HOLDER.set(&PoolHolder { pool1, obj_cell }, || {
            autoreleasepool(|_pool2| {
                POOL_HOLDER.with(|pool1_holder| pool1_holder.autorelease(NSObject::new()))
            });

            let obj: &NSObject = obj_cell.get().unwrap();

            // If we manage to get here, then that's unsound, since the object
            // would have been released in the second autorelease pool.

            println!("{obj:?}");
        });
    });
}
added a commit that references this issue on Aug 8, 2024

Auto merge of rust-lang#13231 - Jarcho:no_tree_walk_in_const, r=Alexe…

Zenithsiz

Zenithsiz commented on May 6, 2025

@Zenithsiz

Is it possible to make an auto trait not depend on the fields of a type?

I'd like to be able to have an auto trait auto trait Everything {}, and a type struct Wrapper<T>(T) such that an auto-trait is always implemented for Wrapper regardless of T: Everything (unless there's an explicit impl !Everything for Wrapper<...> {}).

Further motivation

I have a normal trait with a default implementation depending on an auto trait:

pub auto trait WantsAutoSet {}

pub trait Set {
	fn set(&self);
}

// "Default" impl for all types.
impl<S: WantsAutoSet> Set for S {
	fn set(&self) {}
}

I then have 2 types, Normal which just wants the default impl and Special which wants to do a special implementation:

// Wants default `Set` impl
pub struct Normal<T>(T);


// Wants special `Set` impl
pub struct Special<T>(T);

impl<T> !WantsAutoSet for Special<T> {}

impl<T> Set for Special<T> {
	fn set(&self) {}
}

The issues happen downstream, where a consumer of Normal cannot call set on a generic T, only on T: WantsAutoSet:

// Works
fn set_normal_with_explicit_auto<T: WantsAutoSet>(normal: Normal<T>){
    normal.set();
}

// Does not work
fn set_normal<T>(normal: Normal<T>) {
    normal.set();
}

(See the playground)

This can be fixed by just using impl<T> WantsAutoSet for Normal<T> {}, (or by having Normal not have a type parameter), but I have a lot of these Normal-like types and I don't want to need to make this impl for each one, since at that point I'd just make it a normal trait instead of an auto trait.

Also these Normal-like types do need ownership of T for the Set impl, I can't just use PhantomData to wave it away somehow.

(Also note that this is somewhat like specialization, but I can't just use actual specialization because the Special impl of Set has different bounds that are incompatible (i.e. not a subset), and I'd also like to "disable" the default implementation because it'd inefficient for Special, which wouldn't work with specialization.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-trait-systemArea: Trait systemA-type-systemArea: Type systemB-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.B-RFC-implementedBlocker: Approved by a merged RFC and implemented but not stabilized.B-unstableBlocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCF-auto_traits`#![feature(auto_traits)]`P-mediumMedium priorityS-tracking-perma-unstableStatus: The feature will stay unstable indefinitely.T-langRelevant to the language teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @mitsuhiko@flaper87@Zoxc@steveklabnik@alexcrichton

        Issue actions

          Tracking Issue for auto traits (auto_traits) -- formerly called opt-in built-in traits (optin_builtin_traits) · Issue #13231 · rust-lang/rust