There's a reason my toy language strongly separates the item signature from the body bindings. To abuse Rust syntax, function items look more like
fn foo(Wrapping<i32>) -> Foo = |arg| { … };
where you assign a closure to the item declaration.
I don't think this is a usable choice for Rust — not only would it a giant syntax shift for little/no semantic benefit, but the argument pattern names serve a documentation purpose, even if they aren't a semantic part of the function signature.
Elision of type pattern/literal names when they can be inferred is I think a much better approach for Rust. Using the .{ .. }
syntax, while I doubt it'd be the style rustfmt chooses, OP's example could be written as:
fn new() -> MyCoolStruct {.{
member1: 3,
member2: "Feature Request",
}}
The rustfmt+clippy style would most likely become
default tall style
impl MyCoolStruct {
pub fn new() -> Self {
Self::default()
}
}
impl Default for MyCoolStruct {
fn default() -> Self {
.{
member1: 3,
member2: "Feature Request",
}
}
}
max wide style
impl MyCoolStruct {
pub fn new() -> Self { Self::default() }
}
impl Default for MyCoolStruct {
fn default() -> Self {
.{ member1: 3, member2: "Feature Request" }
}
}
with delegation
impl MyCoolStruct {
pub use Self::default as new;
}
impl Default for MyCoolStruct { /* … */ }
This shouldn't be overlooked. If a type is PODish and has all public fields, the canonical "constructor" is struct literal syntax. You don't need a fn new()
. It's a nice convenience to have, especially if it's const
, since Default::default
can't be (yet), but it's far from a necessity, especially if it doesn't have any special documentation attached to it.
And in fact, even if there are private fields, the canonical "constructor" is still the struct literal syntax, as all other construction ultimately delegates to it (or transmute
). Things can get a bit convoluted depending on exactly how strictly you want to apply terms, sure, but the important takeaway should be that fn new
, while conventional, isn't special in any way and is just like any other function; adding special syntax for "constructor" functions is thus undesirable because it makes them look special when they aren't.
While it is definitely an interesting project to extend Rust in this direction, and guaranteed emplacement would certainly be a powerful tool for performance optimization, Rust's semantics are heavily based on the concept that objects can always be trivially relocated and the address of data isn't meaningful except if it's borrowed at that address.
digression on such
Perhaps unintuitively, a significant portion of times where optimization fails to eliminate "redundant" copies is actually because the address identity properties being preserved are actually too strong! The canonical example:
let mut place = MaybeUninit::uninit();
init(&mut place);
let mut value = place.assume_init();
utilize(&mut value);
place
and value
are semantically required to have disjoint addresses because both places are live simultaneously. Unless the compiler can prove that the pointer/address provided to either init
or utilize
are never captured, then it must preserve the two places' disjointness, because the behavior of the code might somehow depend on that property.
And no, lifetime analysis is not sufficient to justify nocapture
LLVM semantics if the lifetime isn't captured. Even if we were to ignore that all currently proposed models give lifetimes no semantic meaning, we want signatures like ptr::from_ref
to be validly usable, and that signature doesn't even imply a requirement that '_
only encompasses the call, but instead that it outlives the call. Adding semantic meaning to the exact start/end timing of a lifetime is a terrible idea because the language only permits discussing bounds on any specific lifetime region.
potential solutions
A personal pet concept is to allow writing (move place)
as an expression that explicitly moves the value from the place and deallocates the place, even if the type is Copy
, thus allowing a new place to potentially reuse the address of the old place. It would need to be more complicated than just that because of partial moves and non-stack places, but that's the general idea — when you aren't doing tricky things with a place and the optimizer fails to deduce that, you can tell it. Similar thoughts apply to explicit become
tail calls deallocating stack places earlier.
The other pet concept is to just say that addresses can be logically disjoint but map to the same usize
. There are various ways to model this with different tradeoffs (allocation "planes" being likely the most realistic), but they're all either only weakly applicable ("observing" the address in any meaningful way will require the allocation to exist in the single physical allocation plane) or break "obviously true" properties (e.g. cmp(ptr1, ptr2) == cmp(ptr1.addr(), ptr2.addr())
; see guaranteed_eq
for a real example of this) and in-use techniques that rely on such properties (e.g. futexes).
I don't doubt there're ways Rust could be extended in a backwards-compatible manner to support ?Move
and/or ?Drop
types. But first class support, even if 100% compatible, is a significant enough semantic shift that it's as close to a "Rust 2" as the Rust project is likely to ever legitimately consider. Even extensions which are theoretically simpler like ?MetaSized
or even just using A: Allocator
generics for containers are pushing the boundary of what's "spirit of the law" compatible into "letter of the law" territory.
If "exotic" types are confined to only being supported by dedicated "exotic" containers, then first-class support isn't necessary nor particularly useful, and library support for encapsulating and enforcing the additional requirements such as provided by Pin
and the moveit crate are plenty sufficient. Of course, other features like projection or &move
can hugely improve such "second class" library-level support, but they're useful for more regularly shaped code as well.
(This gets far off the OP topic, so if you want to discuss on this concept further please open a new thread here (or on Discord or Zulip) and ping me.)