# Effect as bounds on behavior
Niko's proposal made me rethink how I conceive of effects. Allow me to riff on a mental model focused on subtractive effects like `const`, `nopanic`, `noalloc`, `nothreads` etc.
## Frame
Effects, like lifetimes, could be inferred whole-program; we choose not to. A signature is therefore a contract: by writing `const fn`, I:
- commit (compiler-checked) to never do a runtime-only operation in my function;
- which allows you to use my function in const context.
`const impl` is similar: I commit to a bound on this impl and future changes of it, which you can therefore rely on.
## Traits
Without traits, all is easy. The fun starts with traits:
1. A trait declaration may want effect bounds on some or all methods (`const fn method();`);
2. A trait implementation may want a stricter bound than the declaration (`const impl Default for u32`);
3. A trait declaration may add future default methods, hence must commit to a bound on these methods' effects;
- An impl thus cannot declare a stricter bound than this since it may unknowingly carry these default methods in the future.
4. A function may want to bound the effects of a trait parameter (`T: const Default`);
5. A function may have behavior conditional on its trait bound methods' behaviors, because it intends to call those methods (`~const fn foo<T: ~const Default>()`);
6. `Drop` is normally implicit but we must be able to talk about its runtime behavior; `T: const Destruct` and `T: ~const Destruct` serve this purpose.
Design decision: trait bounds cannot talk about the behaviors of individual methods (RTN-like), they can only talk about a global bound on all method behaviors.
### Nightly behavior in this model
(I think!)
- a `const fn` can have `T: ~const Trait` bounds, and declares it will never do runtime behavior outside of the behavior of the trait methods on such `T`s;
- a `T: const Trait` bound requires an impl that declared none of its methods would ever do runtime operations;
- this implicitly requires the trait declaration to have declared that future default methods would also not do runtime operations.
- a trait can individually bound each of its methods to be `const`, just like normal fns. This is orthogonal to constness of the trait itself;
- a `const trait Trait` can have `T: ~const OtherTrait` bounds, and declares that none of its default methods will ever do runtime behavior outside of the behavior of the methods from the `~const OtherTrait` bounds.
- note that this is only a commitment on default methods;
- this is necessary for impls of this trait to commit to a bound on its methods.
- `const impl Trait for X` can have `T: ~const OtherTrait` bounds, and declares that none of its methods will ever do runtime behavior outside of the behavior of the methods from the `~const OtherTrait` bounds.
- because of future default methods, this requires the trait to be declared `const trait`.
### Niko's proposal in this model
Niko's proposal doesn't quite fit this model, because it allows methods to opt-out of the bound on the whole trait/impl.
### Why `~const fn`?
This raises a question: what's the point of writing `~const fn` in Niko's proposal?
Can we somehow make `const fn` be a usefully stronger commitment than `~const fn`? (Note that this model in this doc does _not_ allow for "function that is only callable at compile-time").
### Why `const trait`?
`const trait` is the most confusing part of this. One may think it constrains impls or methods but no! It's only about _default methods_. I 100% expect users to assume that "`const trait ` => the methods are `const`".
Even this shuold be fine:
```rust!
const trait Trait {
// The default body is `const`, but implementors may provide a non-const implementation
fn method() -> u32 {
42
}
}
```
### More effect bounds
`nopanic` would be awesome for unsafe code, the ability to temporarily move out of `&mut T`, and linear types.
I can imagine other bounds being useful for unsafe code in general, very curious if y'all have ideas. See [Zulip thread](https://rust-lang.zulipchat.com/#narrow/channel/328082-t-lang.2Feffects/topic/Fine-grained.20effects.20for.20safety).
### Notation for conditional trait bounds
The real question is:
```rust!
nopanic fn foo<T: Default>() -> T;
```
Should `nopanic` extend to the trait bound? Is it ok for this function to panic if `default()` panics? Is it ok if this function can't call `default()`?? Is this like imperfect derive???
```rust!
impl<T: Default> Option<T> {
// Here the API is clearly that the "Default" impl is something external, so I naturally understand `nopanic` to mean "as far as the implementation of this simple function is concerned".
pub nopanic fn unwrap_or_default(self) -> T { ... }
}
```
-> maybe intuition pump with `nopanic Trait<T>`, do we expect propagation to bounds on `T`?
-> maybe the real question is what happens with multiple effects:
```rust
pub nopanic noalloc noloop const unwrap_or_default<T: Default>(self) -> T
```
would be madness to replicate the bounds.
In which cases to I actually want the unconstrained bound? If I'm not calling a method.