Skip to content

#[target_feature] mismatch on unsafe trait fn vs its impl causes sneaky UB #139368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
obi1kenobi opened this issue Apr 4, 2025 · 22 comments
Open
Labels
C-bug Category: This is a bug. F-target_feature_11 target feature 1.1 RFC I-lang-radar Items that are on lang's radar and will need eventual work or consideration. I-prioritize Issue: Indicates that prioritization has been requested for this issue. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@obi1kenobi
Copy link
Member

I tried this code: (playground)

// This trait is unsealed. Imagine it's in crate A.
pub trait Example {
    unsafe fn demo(&self) {}
}

// Imagine this type is in crate B,
// which depends on A.
pub struct S;

impl Example for S {
    // This isn't applied to the trait's fn!
    #[target_feature(enable = "avx2,aes")]
    unsafe fn demo(&self) {}
}

// Imagine this function is in crate C,
// which also depends on A but might or might not be used
// together with A.
//
// It seems to be impossible to write the safety comment below.
pub fn accept_dyn(value: &dyn Example) {
    // SAFETY: umm ???
    // Who knows what #[target_feature]
    // attributes the trait impl has imposed?!
    unsafe { value.demo() }
}

The "imagine this type is in crate X" is not strictly necessary — this is still a problem within a single crate too. It's just much easier to spot the problem when the code is all together instead of in 3 different and separately evolving crates.

I expected to see this happen: to soundly use unsafe items from a trait, reading the safety comments on the trait and its items should generally be sufficient. A safe (non-unsafe) attribute shouldn't be able to be used in a manner that causes hard-to-find unsoundness and UB. This code should either be rejected outright for tightening the trait's safety requirements in the impl, or at least require #[unsafe(target_feature)] plus raise a lint for the requirements-tightening.

Instead, this happened: this code compiles fine with no warning. The attribute is not unsafe. Even an unintentional "editing error" is likely to silently cause impls' requirements to diverge from the trait's requirements and lead to unsoundness.

Meta

rustc --version --verbose:

1.88.0-nightly (2025-04-02 d5b4c2e4f19b6d703737)

From the playground.

@obi1kenobi obi1kenobi added the C-bug Category: This is a bug. label Apr 4, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Apr 4, 2025
@bjorn3 bjorn3 added I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness F-target_feature_11 target feature 1.1 RFC labels Apr 4, 2025
@rustbot rustbot added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Apr 4, 2025
@bjorn3 bjorn3 added I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness I-prioritize Issue: Indicates that prioritization has been requested for this issue. and removed I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Apr 4, 2025
@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Apr 4, 2025

I agree this can be a footgun: the safety precondition implied by the #[target_feature(enable=...)] may be missed because it didn't occur to the author when they added the unsafe. I don't think this is a language soundness issue in the technical sense: ultimately there's no UB unless a caller writes unsafe {} to call the method, and they can do so soundly if they know the safety conditions. It's just easier than it should be for them to not know those conditions, and that's worth addressing to make it easier to write sound unsafe code.

Unfortunately, it seems difficult to automatically distinguish mistakes from sound usage:

  • The safety contract of the method may, in fact, already require the caller to ensure the target features required by the respective impl are available. memchr's internal Vector trait kinda does this for abstracting over different SIMD instruction sets. In that case the impls don't actually add #[target_feature] themselves, but the trait still requires callers to ensure the target features of the relevant impl are enabled. (This trait can afford to be vague about which target features those are because it's for internal use only and only used via static dispatch, so the rest of the library can actually ensure this.)
  • The impl in question may, in fact, discharge the safety precondition itself, e.g., by ensuring values of the Self type are only constructed if the target features are available. It's a bit unfortunate that this requires marking the trait method as unsafe (possibly with a safety precondition of "this is always safe to call, ignore rustc"). However, the trait method is the optimal place to introduce the target feature in the case of dynamic dispatch. You could write the method body as unsafe { the_actual_impl(self, arg1, arg2, ...) } where only the_actual_impl has the target feature, but then that call can't be inlined due to mismatched target features, so anyone calling the method through the vtable would unnecessarily do two calls (first an indirect one to the method, then another one to the_actual_impl).

Making the attribute unsafe is not quite right: ultimately UB only comes from a call to the method, which requires the caller to write an unsafe block. This is different from existing unsafe attributes, which can introduce UB just by applying them to an item, even if the item is never used at runtime. In contrast, #[target_feature] is only another way to add a precondition for an unsafe function, not by itself unsafe to invoke. This is similar to how an unsafe fn can lay out safety preconditions for callers without any unsafe blocks or attributes in the immediate vicinity. Vec::set_len is the canonical example: only unsafe-to-call because it's relevant for Vec's safety invariant, not because it does anything unsafe on its own. If and when unsafe fields are adopted, this could change. But unsafe fields are a tool to support writing unsafe code more confidently, they weren't needed to plug any soundness holes. I see this issue similarly.

In analogy to unsafe fields, I could see an argument for making #[target_feature] some kind of unsafe attribute. It doesn't need to be unsafe for soundness, but there's a trend in Rust's evolution towards more fine-grained annotations for both introducing and discharging safety-related proof obligations (unsafe fields, unsafe_op_in_unsafe_fn lint, unsafe extern blocks with a mix of safe and unsafe items, etc.). From this POV, it might make sense to call out #[target_feature] separately in the same way that unsafe fields are useful for keeping track of where safety invariants need to be upheld.

@obi1kenobi
Copy link
Member Author

I agree with substantially everything you said.

I'm curious about your opinion on a possible rustc warn-by-default lint for when a trait impl introduces more target feature requirements than the pub trait being implemented.

@jieyouxu jieyouxu added T-lang Relevant to the language team, which will review and decide on the PR/issue. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 5, 2025
@hanna-kruppe
Copy link
Contributor

I think that would definitely be good idea as a clippy lint. The bar for rustc lints is much higher and it’s always possible to uplift a clippy lint into rustc later. This pattern doesn’t always indicate a mistake, and when it’s actually what you want I don’t think you can silence the lint by writing the code slightly differently, so this lint would probably end up allowed/expected in some places.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 5, 2025

Maybe the attribute should be unsafe to apply to impl methods. rust-lang/rfcs#3715

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Apr 5, 2025

RFC 3715 is interesting because:

  • #[derive(unsafe(Trait))] means unsafe in the sense of “proof obligations are discharged here” — consistent with existing unsafe attributes
  • #[proc_macro_derive(Trait, unsafe)] uses a slightly different syntax for the “introduces proof obligations for other parts of the code” kind of unsafe

Target feature attributes are the latter kind of unsafe so perhaps it should be spelled #[target_feature(enable=…, unsafe] rather than #[unsafe(target_feature(…))].

Note, however that it’s really only relevant for trait impls, not for inherent impls or free functions — without traits in the mix, there’s only one place where preconditions are defined and the caller always knows precisely which item will be called. Requiring the unsafe token in those contexts is pointless and would negate the recently stabilized “target feature 1.1” improvements. I’m not sure how I feel about making the attribute even more context dependent for the sake of what’s effectively a lint.

@bjorn3
Copy link
Member

bjorn3 commented Apr 5, 2025

Target feature attributes are the latter kind of unsafe so perhaps it should be spelled #[target_feature(enable=…, unsafe] rather than #[unsafe(target_feature(…))].

I did say it is actually the former kind of unsafe. The #[unsafe(target_feature)] discharges the safety requirements of the intrinsics called within this method based on the safety requirements of the method declaration in the trait definition.

@obi1kenobi
Copy link
Member Author

I agree with this. And so long as it only discharges those requirements, I think #[unsafe(target_feature)] sounds great.

My remaining concern is that #[target_feature] on impls of trait methods can also be the other kind of unsafe too: they can add features to the list specified in the trait definition, which users of dyn Trait or impl Trait are likely to be unaware of. This feels at minimum like a candidate for a clippy::suspicious lint to me, and possibly worth something akin to #[target_feature(.., unsafe)] for the "additions" list as well.

My use of syntax above is in reference to the feature itself, not a vote for a particular syntactic choice. I'll leave those to people way more qualified than me :)

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Apr 5, 2025

I suppose this is a matter of perspective. While drafting my first comment in this issue, I was leaning in the same direction for a while. But ultimately I think it's more useful to view it only as introducing a precondition the caller has to fulfill, because that's the approach that works across the entire language and doesn't require any special pleading for traits.

It's language UB to call a #[target_feature] function if the target features aren't actually available at the time of the call, even if the function doesn't call any intrinsics. For example, it's UB to call #[target_feature(enable = "avx2")] fn nop() {} without having AVX2. But there's no UB if the function is never called! Thus, the attribute can't be only about discharging requirements for other operations on the body, it also imposes a safety requirement on the function being called at all. In particular, it's not possible for a #[target_feature] function to make itself safe-to-call by doing something like assert!(is_x86_feature_detected!("avx2")); before doing its actual work. (It is possible with #[cfg], of course, but that's the far less interesting case.)

We may then use this precondition to call other functions requiring the same target feature, which is why such calls are sound: we're only propagating what we got from the caller. Before target feature 1.1, this was annoyingly explicit: every function with #[target_feature] was unsafe to call, and inside them you'd again wrap the calls to other target feature functions (including but not limited to intrinsics) in an unsafe {} block to call them. With target feature 1.1, the simple and mechanical cases are effectively inferred. But the preconditions are still there:

  • Calling a safe target feature function from another function that doesn't have the right target feature attributes still requires unsafe {} (caller needs to check that the feature is available).
  • Safe target feature functions can't be coerced to safe function pointers, because then the compiler loses track of who calls it and what target features they have. They also can't occur in trait impls, for the same reason.
  • Global handlers like the panic hook can't be marked with #[target_feature] either, not even if they're declared unsafe, because they need to be safe to call from anywhere in the program. The person implementing those handlers can't unilaterally assert that those callers will ensure the target feature is available, because the callers have no way of knowing about it!

Returning to trait impls: yes, the body of the impl method can assume that the target feature is available and thus freely call other functions that require those features (which the compiler will happily let you do). But the immediate reason for that is the same as for free/inherent functions: calling the method would be UB if the features weren't available (again: even if the method body is actually empty), so in general it's unsafe to call the method and this precondition must be propagated up the call stack. The only thing that's different about traits is where those preconditions should be documented so the caller can see them.

@obi1kenobi
Copy link
Member Author

obi1kenobi commented Apr 5, 2025

The only thing that's different about traits is where those preconditions should be documented so the caller can see them.

This is kind of a key thing though, no?

If a trait specifies a safe function, an impl can't (and shouldn't be able to) make that function unsafe by adding new safety obligations to the (previously empty) set.

In this case, it sounds like you're implying that adding new safety obligations is fine because the set wasn't originally empty this time. That doesn't seem right to me.

So I wouldn't agree it's just a documentation issue, especially if no concrete way to resolve the issue purely at the level of documentation is presented. I don't see a feasible way for this to be solved at the documentation level alone, and I'm skeptical one exists at all.

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Apr 5, 2025

I'm not implying unilaterally adding a new safety precondition at the impl level is "fine". A Rust program written that way isn't sound. A trait impl defining an unsafe-to-call method can only rely on the preconditions stated by the trait's documentation for that method, not add new ones that it would like to have. But at the language level, it seems most useful to me to classify #[target_feature] as only introducing a precondition. That's consistent with how it works for free functions and inherent impls (which don't have another source of preconditions they need to match) and sufficient to explain why there can be language UB if the trait impl goes beyond what the trait definition documented. The challenge of ensuring that trait definition and trait impl agree on what the preconditions are isn't really unique to target features. It may appear trivial in most cases, but impls are allowed to refine the safety preconditions stated by the trait (including dropping them entirely, once #100706 is stabilized). As a silly example:

trait Foo {
    /// # Safety
    /// `n` must be a prime number.
    unsafe fn foo(n: u32);
}

impl Foo for () {
    /// # Safety
    /// `n` must be odd. Note that `Foo` requires prime numbers, but
    /// composite odd numbers are also fine for this impl.
    unsafe fn foo(n: u32) {
        // ...
    }
}

This impl is unsound because the author of the impl forgot that there is an even prime number, so their supposed refinement of Foo::foo's safety precondition is actually introducing a new precondition not documented in the trait. This might lead to language UB in the method body due to some unsafe {} block that relies on the illegitimate precondition. But the method might also consist entirely of safe code, e.g., storing the value of n somewhere. This might still ultimately result in UB if the stored value is later used elsewhere in an unsafe {} block that discharges its obligation by arguing:

SAFETY: n must be odd here. It came from a call to <() as Foo>::foo and we haven't modified it since then. Thus, the caller of that function must have ensured that n is odd.

In such a case, I'd still assign the blame for the resulting UB to the fact that the author of impl Foo for () incorrectly refined the precondition. And yet, the impl doesn't contain any unsafe keyword which we might blame for incorrect discharging an obligation (only one for affirming that foo has some safety preconditions). The only occurrence of unsafe in that impl is in the "imposing precondition on the caller" sense, and the mistake is purely in doc comments.

In that sense, #[target_feature] is better because it's at least compiler-visible that a precondition is being introduced. @bjorn3 is right that the person writing the impl should check that the precondition stated on the trait actually implies all the preconditions on the impl's version of the method. A lint or a modification of the attribute syntax to include the unsafe token may help with this. But I don't think it's useful to extend the "discharging obligations" meaning of unsafe to also cover introducing and refining preconditions. Those two aspects are separate but complementary: soundness can't be established solely by focusing on the parts that discharge obligations, the surrounding code that defines safety-relevant pre- and postconditions also matters, even if it is safe code.

@obi1kenobi
Copy link
Member Author

Broadly, I'm onboard with that 👍 In particular, I agree that incorrect refinements should be considered unsound, and that only the trait's safety requirements should be relevant to the caller.

Just one thing I'd push back on:

the person writing the impl should check that the precondition stated on the trait actually implies all the preconditions on the impl's version of the method.

I think "the Rust way" is that whenever possible, built-in tooling (rustc, clippy, etc.) should check this instead of relying on humans. It obviously isn't possible every time (e.g. the odd numbers and primes example), but with #[target_feature] it definitely is possible and I think we should do it.

Between a rustc lint and a clippy lint, I personally would advocate for a rustc lint because I believe we've all agreed this is an unsoundness issue. But if you all think it should go to clippy instead, we can move the issue there.

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Apr 5, 2025

To be clear, I do not agree this is a language soundness issue. As stated before, I'd say it's a "language makes it hard to write sound unsafe code" issue, like the motivation for unsafe fields. You may call that a soundness issue, but it's different from when rustc emits incorrect code or the type checker lets you write transmute without any unsafe tokens.

I think it would be completely defensible to introduce an unsafe token in the attribute syntax (as long as it's only required in trait impls). I just believe that it's best understood as the "introducing preconditions" kind of unsafe, not a new flavor of discharging. Either way the unsafe token would give impl authors a chance to realize that they're adding another precondition by writing this attribute (even/especially if they haven't internalized the precondition/discharging distinction). Note that even then, the compiler can only prompt the author to check their work and pinky-promise that they're refining the trait method's precondition. It's not possible to remove the need for a human to check this even with #[target_feature] -- the compiler knows that the attribute introduces a precondition, but can't automatically relate it to the other preconditions stated in comments.

But requiring the unsafe in the attribute that is a backwards incompatible change so it would need to wait for the next edition. In the meantime, a lint is useful. Since the bar for rustc lints is relatively higher and clippy already has many lints aimed at preventing mistakes in unsafe code, I'd suggest implementing it in clippy is a natural first step.

@obi1kenobi
Copy link
Member Author

Sorry for the confusion — my opinion is "the impl is unsound and should be flagged" and not "this Rust feature is unsound and should be rolled back."

@zachs18
Copy link
Contributor

zachs18 commented Apr 6, 2025

I think there's a more general issue here, also: having any unsafe fn in a safe trait makes it unclear who needs to uphold what preconditions, and if impls are allowed to restrict the documented preconditions of unsafe fns in the trait (especially when calling that unsafe fn on dyn Trait)

Generally, unsafe code cannot rely on the implementation of safe traits to be correct for soundness, but if that is true even when a safe trait Trait contains an unsafe fn bar, then it can never be sound to call Trait::bar on an arbitrary1 dyn Trait (or arbitrary ConcreteType: Trait), regardless of any target_features, because there's no way to know the actual safety preconditions of <ConcreteType as Trait>::bar since they could be "incorrect" with respect to the trait's documentation.

In @hanna-kruppe's prime example:

trait Foo {
    /// # Safety
    /// `n` must be a prime number.
    unsafe fn foo(n: u32);
}
impl Foo for () {
    /// # Safety
    /// `n` must be odd. Note that `Foo` requires prime numbers, but
    /// composite odd numbers are also fine for this impl.
    unsafe fn foo(n: u32) {
        // ...
    }
}

If it is true that implementing a safe trait incorrectly is not unsound, then impl Foo for () is not unsound, but any caller of <() as Foo>::foo needs to uphold <() as Foo>::foo's documented safety preconditions, not Foo::foo's (and thus cannot pass 2). Though of course restricting the precondition of an unsafe fn in a safe trait is still a footgun, it might not be unsound.


For a very explicit example, this code invokes UB (calls std::hint::unreachable_unchecked), so it must be unsound, but where is the unsoundness?

// crate a
pub trait Foo {
  // SAFETY: this is always safe to call // *1
  unsafe fn bar();
}

// crate b depends on crate a
pub struct LocalType;
impl crate_a::Foo for LocalType {
  // SAFETY: this is never safe to call
  unsafe fn bar() {
    // SAFETY: <() as Foo>::bar is never safe to call, so this is unreachable
    unsafe { std::hint::unreachable_unchecked() } // *2
  }
}

// crate c depends on crate a
pub fn quux<T: crate_a::Foo>() {
  // SAFETY: as per Foo::bar's docs, Foo::bar is always safe to call
  unsafe { <T as Foo>::bar() } // *3
}

// bin crate depends on all others
fn main() {
  crate_c::quux::<crate_b::LocalType>(); // *4
}

It cannot be *4 because that is safe code.

If Foo were an unsafe trait, then it would definitely be *2 (since restricting the preconditions on bar would be an incorrect implementation of Foo, which is unsound for unsafe traits), but since Foo is not an unsafe Trait, it could be argued that implementing it incorrectly shouldn't be unsound, so the error is at *3.

It could also be argued that unsafe fn in safe trait impls can have arbitrary preconditions unrelated to the trait's documentation (since implementing a safe trait "wrong" cannot be unsound), so the unsoundness is at *1 since it lies about the precondition of <Arbitrary as Foo>::bar when it can't actually know the precondition.


Basically, *2 in my example corresponds to the #[target_feature(enable = "avx,aes")] in the OP example, *3 corresponds to unsafe { value.demo() }, and *4 corresponds to whatever calls accepts_dyn.

In the OP example, the #[target_feature(enable = "avx,aes")] adds a precondition to <S as Example>::demo, that (presumably2) is not allowed by the documentation of Example::demo.

If Example in the OP was an unsafe trait (and it documented fn demo's preconditions), then the #[target_feature] would be the source of the unsoundness, but Example isn't an unsafe trait, so it's a bit unclear.


I think this discussion is perhaps evidence that allowing unsafe fn in a safe trait is unclear and perhaps could be phased out over an edition (only defining safe traits with unsafe fn, implementing them would need to stay for backwards-compatbility to implement traits from older crates in newer editions).

Edit: There are some unsealed safe traits in the stdlib with unsafe fn: std::os::fd::FromRawFd::from_raw_fd and FromRawHandle/FromRawSocket on Windows. I'm not sure if the intent is that implementors can or cannot impose additional restrictions (e.g. can <MyOsSpecialFileType as FromRawFd>::from_raw_fd require that the passed fd is actually that special file type for soundness?). The existing impls in the stdlib definitely don't impose such restrictions (since they are mirrored with safe-to-call From<OwnedFd> impls).

Footnotes

  1. Ignoring things like sealed traits for the moment; assume Trait is pub and possible to implement outside its crate.

  2. Technically, since Example::demo has no safety documentation, it's impossible to soundly call it at all, but I'm assuming it was just omitted for brevity.

@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Apr 6, 2025

I agree that the vast majority of traits with unsafe methods probably should be unsafe, and the exceptions are very subtle. That said, "the blame can't lie in safe code" is a useful heuristic but not 100% right in general.

For example, BTreeMap's CursorMut has several unsafe methods that require the caller to uphold order and uniqueness of the keys in the map/set. This is not required for BTreeMap's own soundness (it already has to deal with broken Ord impls because that's a safe trait) but only so that BTreeMap can provide a conditional invariant to third-party unsafe code: if you put keys with a correct Ord into a map, you can rely on the keys being unique and sorted. This is a safety-relevant guarantee that BTreeMap chooses to provide, regardless of its own internal use of unsafe. If BTreeMap was implemented in 100% safe code and documented this guarantee, but forgot to mark the key-modifying methods as unsafe, then any resulting UB from unsafe code relying on that guarantee is arguably the fault of BTreeMap.

Of course, unsafe code authors also need to be careful and selective about when and how they trust safe code to be correct. It's definitely wrong to rely on an "open world" set of safe code, such as all impls of a safe trait or any value of a function pointer type, to be correct. That would make compositional soundness proofs impossible, and those are the big advantage of Rust over "just write C and prove that it's memory safe 🙃". But it also doesn't scale to say that unsafe code authors can only trust code they wrote themselves in the same crate. For starters, you often have to trust standard library APIs -- and the standard library shouldn't be unique in this respect. Rust doesn't currently have any way to say "I'm typing unsafe here to promise that I'm actually implementing my documented invariants correctly in safe code" so we're left with situations where the correctness of a 100% safe crate can matter for soundness of unsafe code in other crates.

@apiraino
Copy link
Contributor

apiraino commented Apr 7, 2025

Nominating for T-lang discussion (hope it's the correct procedure to get team's attention!)

@rustbot label +I-lang-nominated

@rustbot rustbot added the I-lang-nominated Nominated for discussion during a lang team meeting. label Apr 7, 2025
@scottmcm
Copy link
Member

scottmcm commented Apr 9, 2025

Generally, unsafe code cannot rely on the implementation of safe traits to be correct for soundness, but if that is true even when a safe trait Trait contains an unsafe fn bar

But if an implementation of a safe trait uses an unsafe block (aka a hold_my_beer block) then it's that implementation that's responsible for the correctness of that unsafe block under the trait's preconditions.

impl Foo for Bar {
    unsafe fn foo() { unreachable!() }
}

has to be sound, since it's not discharging any obligations.

So in your example, I'd say it's necessarily *2 that's at fault.

@traviscross traviscross removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Apr 9, 2025
@tmandry
Copy link
Member

tmandry commented Apr 9, 2025

We discussed this in today's lang team meeting, and the consensus was that we should disallow impl methods with more target features enabled than those in the trait definition. Additionally, impls with fewer target features than in the trait definition should fire a refining lint. We would also be okay with disallowing any difference between the trait and impl target features for now, adding the ability to refine later.

Hopefully we can get away with a small breaking change here, otherwise we can claw it back with an FCW, lints that transition to an error over an edition, etc.

Another aspect we discussed is that possibility that some users might actually want to do this, and while there is a workaround of unsafely calling another function with different target features from the impl, the behavior of inlining around target features might create a performance regression for them. If this does come up we can discuss the possibility of adding a new unsafe attribute to allow such a difference between the trait and impl, but until a use case comes up we aren't discussing it yet.

@traviscross traviscross added I-lang-radar Items that are on lang's radar and will need eventual work or consideration. and removed I-lang-nominated Nominated for discussion during a lang team meeting. labels Apr 9, 2025
@hanna-kruppe
Copy link
Contributor

hanna-kruppe commented Apr 9, 2025

We discussed this in today's lang team meeting, and the consensus was that we should disallow impl methods with more target features enabled than those in the trait definition.

Not idea whether it makes a difference for your decision, but note that #[target_feature] currently can't be applied to required methods, only to provided methods. So unless and until such a language feature is added, this amounts to banning #[target_feature] entirely on trait methods that can't or shouldn't have a default body.

@traviscross
Copy link
Contributor

Anyone interested in making that proposal for us?

cc @veluca93

@RalfJung
Copy link
Member

I'm quite surprised we allow #[target_feature] in trait impls at all. That seems like an accident?

👍 for FCW'ing when an imply restricts the target features compared to the trait declaration -- us just FCW'ing all uses of #[target_feature] in trait impls, if that's a possibility.

@veluca93
Copy link
Contributor

veluca93 commented May 14, 2025

Sorry, I completely missed this ping - I am also fairly surprised we allow #[target_feature] on (unsafe or not) trait methods...

IMO a FCW seems like a reasonable way forward (and I'd be willing to try it out when I have time)

I also think a unsafe version of the target_feature attribute would be great for other reasons, so I'm supportive of trying that out if that turns out to be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. F-target_feature_11 target feature 1.1 RFC I-lang-radar Items that are on lang's radar and will need eventual work or consideration. I-prioritize Issue: Indicates that prioritization has been requested for this issue. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests