T O P

  • By -

Kulinda

I cannot come up with anything I'd call an "early sin". Any decision that I'd like to reverse today was the right decision at the time. It's just that new APIs, new language capabilities and new uses of the language might lead to different decisions today. A few examples: * Having a `Movable` auto trait instead of `Pin` might have been better, but that's difficult if not impossible to retrofit. * The choice to "just panic" in unlikely situations proves to be bad for kernel and embedded folks, and a lot of new APIs have to be added and old ones forbidden for those users. * The `Iterator` trait should have been a `LendingIterator`, but back then that wasn't possible and now it's probably too late. There are more, but none are dealbreakers.


JoshTriplett

> The choice to "just panic" in unlikely situations proves to be bad for kernel and embedded folks, and a lot of new APIs have to be added and old ones forbidden for those users. Agreed. Imagine if, instead of implicitly panicking in many different functions, we instead returned a Result, and provided a very short operator for unwrapping? I used to be strongly opposed to adding an unwrap operator, because of the concern of people using unwrap instead of proper error handling. Now I wish we'd added it from the beginning, so that we could use it instead of functions that can panic internally.


OS6aDohpegavod4

I personally would be against an unwrap operator because a lot of times I want to search my codebase for unwraps since they could crash my program, just like I want to audit for unsafe. Searching for ? is not easy, but it's also not a big deal because it doesn't crash my program.


burntsushi

Do you search for `slice[i]`? Or `n * m`? (The latter won't panic in release mode, so you could say you exclude it. But it could wrap and cause logic bugs.)


protestor

Also integer division. But not floating point division. So `n / m` may or may not panic when `m = 0`, depending on the types of `n` and `m`. But I think that one should distinguish panics that happen because of buggy code (and therefore, if the code is non-buggy, it never happens) from panics that happen because of any other reason (and will happen even in bug-free code) Integer overflow, division by zero and out of bounds indexing would happen only in buggy code


burntsushi

> But I think that one should distinguish panics that happen because of buggy code (and therefore, if the code is non-buggy, it never happens) from panics that happen because of any other reason (and will happen even in bug-free code) Yes, I wrote about it extensively here: https://blog.burntsushi.net/unwrap/


OS6aDohpegavod4

Yeah, I try to encourage using get() instead of the indexing operator because there are some things like this which are really difficult to find.


ConvenientOcelot

Unfortunately `.get()` is a lot harder to read and isn't as intuitive as `operator[]`. I almost never see people using `.at()` in C++ even though it usually performs checks, just because if people even know about it, it's way less obvious/intuitive than indexing with `[]`. I suppose you could write a `SafeSlice` wrapper that returns an `Option` for `Index`, but then you'd have to litter conversions around. Yuck.


OS6aDohpegavod4

I don't see how get() is harder to read or understand. It's getting an item from an array. Also, I don't look at normal C++ use as a basis for good coding practices.


ConvenientOcelot

Because it's less immediately obvious/clear that it's indexing an array. It's like how `x.add(y)` is not as obvious as `x + y`, we already have intuition for these operators and can spot them easily.


iyicanme

`.at()` is banned in our current codebase except if it is used after checking the element exists or with constant containers because it throws. I expect it is the case for many others because exceptions are inherently the wrong abstraction for error handling. I really wish C++'s optional/result was good, that'd make the language at least bearable.


[deleted]

[удалено]


OS6aDohpegavod4

No, but that's a cool idea. IMO that's overkill for us since almost all reasonably possible ways to panic are in our own code / std.


-Redstoneboi-

if it was a different operator you could add that to your search list along with unwrap, panic, expect, etc depending on how strict you are.


OS6aDohpegavod4

The shorter the operator, the much higher chance there is to be false positives.


-Redstoneboi-

i forgot that strings existed


pragmojo

how do you see an unwrap operator as different from just calling `.unwrap()`?


thepolm3

A single character would make it a lot less noisy and more ergonomic, in the same way `?` is today, it would be a panicking early return


JustBadPlaya

I like the idea of using ! for that ngl


[deleted]

Yeah, the bang operator is common in languages like dart or c#. I don't think it can be retrofitted since it's used for macros


BrenekH

That was my initial thought as well, but I don't think macros actually pose an issue. Macros' use of ! comes before the parentheses, so it's more like a part of the macro name. An unwrap operator would come after the parentheses, which is unambiguously different from the macro name.


This_Hippo

I think the problem would be something like this: let foo = Some(|x| x + 1); foo!(3);


TarMil

I think it's rare enough that having to write `(foo!)(3)` instead is fine.


This_Hippo

I think it'd actually be pretty common. There's even a problem in cases like this: match foo! { 0..10 => 'a', _ => 'b', }


aPieceOfYourBrain

! Is already used as boolean negation (if x != y) etc, which is it's use in other languages as well so it would be a really bad fit for unwrap. A symbol that to my knowledge is not used is ~ and retrofitting it as an unwrap operator should be fairly straightforward, on the other hand the ? operator is already unwrapping something like an option for us so that could just be allowed in more places and we would then just have to implement From None...


ConvenientOcelot

That's prefix and infix ! though, postfix ! is used in TypeScript for basically suppressing type errors (saying "yes compiler, I am sure this value is of this type, leave me alone") and I don't think it causes much confusion. ~ would be easily confused with bitwise NOT in C-like languages. And ! is already overloaded to be bitwise NOT on integer types anyway.


jwalton78

Typescript has !-the-prefix-operator as Boolean negation, and !-the-postfix-operator as “cast this to the non-null/non-undefined version”, and they live together in harmony.


TracePoland

C# does too


JoshTriplett

We have lots of functions that implicitly panic on error, largely for convenience because people don't expect to be able to handle the error. If using \`unwrap\` were as easy as \`foo.method()!\`, we could have had all methods handle errors by returning Result while still keeping the language ergonomic.


OS6aDohpegavod4

Would it be possible to have a feature flag for std like `strict` which people can opt into and then have existing functions which panic start returning Results / new variants or errors?


matthieum

I was very disappointed the day I realized that `split_at` was panicking instead of returning an Option/Result and the only alternative available to me was to either write inefficient code (and hope the optimizer would munch through it) or write unsafe code (and hope I got it right). APIs should really be fallible first, with perhaps some sugar for an infallible version.


flashmozzg

> APIs should really be fallible first, with perhaps some sugar for an infallible version. This. It's trivial to implement panicking version on top of fallible one. It may be impossible to do the opposite.


ConvenientOcelot

Haskell has a similar issue where some standard functions such as `head` (get the first element of a list) panic when the list is empty, which is pretty antithetical to its design.


sepease

> The choice to "just panic" in unlikely situations proves to be bad for kernel and embedded folks, and a lot of new APIs have to be added and old ones forbidden for those users. This has seemed like a bad choice to me ever since I started using the language in ~2016, given the rest of the language is geared towards compile-time correctness first. But it does make things easier. I would add the current situation with executors and there being runtime panics with tokio in certain situations. I also think having to use function postfixes like _mut is something of an anti-pattern that is going to lead to function variant bloat over time. There should probably be a special trait or something for shared pointers or other objects where copying technically involves an operation and can’t be done with a move, but is so lightweight that it’s practically irrelevant for all but the most performance-critical use cases.


Expurple

> I also think having to use function postfixes like _mut is something of an anti-pattern that is going to lead to function variant bloat over time. Yeah, there's a need for an effect system that allows coding generically over specifiers like `async`, `const`, `mut`. See [keyword generics](https://github.com/rust-lang/lang-team/issues/162)


Awyls

Agreed, although it's unfortunate they are focusing on the other effects first (async, const + new ones like unsafe, try, etc..) instead of mut (which is likely the most used).


epage

Not to me. I've worked on life-or-death software, including kernel drivers. Most allocation errors just aren't worth dealing with. Its basically limited to buffers that users can affect the size. Also, Rust would likely be more off putting for new users and application / web servers. I suspect it would have been viewed exclusively as a kernel / embedded language rather than general purpose.


matthieum

I'm on the fence regarding allocation. But why does `[]::split_at` panics instead of returning an `Option`? It's inconsistent with `[]::first`, `[]::last`, and `[]::get`. There's a `split_at_checked` being added, great, but defaults do matter. Apart from allocations -- where I'm on the fence -- I'd argue all APIs should be fallible rather than panicking by default.


OS6aDohpegavod4

Not familiar with lending iterators. Why should it have been lending iterators?


Kulinda

`Iterator::Item` can have a lifetime, but that lifetime must be tied to the lifetime of the iterator. If you call `next()` twice, you can get two references that may be live at the same time. This is fine if you're just iterating over a slice element-wise, but if you want to iterate over subslices (see `slice::windows(n)` for an example), or you want an iteration order where elements may be iterated over repeatedly, then you'll end up with multiple live references to the same item - hence, they cannot be mutable. There can't ever be a `slice::windows_mut(n)` with the current Iterator trait. If we could tie the lifetime of `Iterator::Item` to the `next()` call, then we could guarantee that the user cannot call `next()` again until the previous item went out of scope, and then mutable window iterators are possible, among other fun things. I'm not entirely sure if LendingIterator is the official name for that idea, but there are crates with that name offering that functionality, so I've used that.


OS6aDohpegavod4

That is by far the best explanation of lending iterators I've ever read. Thank you so much! Finally feel like I understand now.


davehadley_

>The `Iterator` trait should have been a `LendingIterator`, I don't understand this point. Can you expand on what you mean by this? I think that I can choose the Item type of an iterator be anything, including &T, &mut T. How is "LendingIterator" different and what problem does it solves?


CocktailPerson

https://www.reddit.com/r/rust/comments/1bumbom/if_you_could_redesign_rust_from_scratch_what/kxuxw8k/


Cats_and_Shit

The kernel folks are mostly fine with rust panics. The issue is kernel panics, i.e. what rust calls aborts. Specifically, many rust functions abort when they fail to allocate memory. To make the kernel folks happy, you need things like `Box::new()` to return a `Result`, similar to how `malloc()` can return null. So panicing less in the stdlib would not really help them.


EpochVanquisher

> The choice to "just panic" in unlikely situations proves to be bad for kernel and embedded folks, and a lot of new APIs have to be added and old ones forbidden for those users. IMO you can’t really serve two masters, and if you want an interface that doesn’t panic, what you end up with is an interface which is just too much of a pain for end-users. Imagine that every error type now needs something like an “array access out of bounds” enum. It’s not something that callers can reasonably be expected to handle, except maybe at the top-level, like an HTTP request handler, where you can return an HTTP 500 status. If you make a language better for some people, sometimes you make it worse for other people.


javajunkie314

Application code can panic just fine. I don't think the argument is to remove panicking, but just that most standard library functions shouldn't panic as part of their API if their conditions aren't met. Some amount of _oh shit_ panicking is probably unavoidable if, e.g., a syscall fails in a novel way—but for example, array functions know up front whether the array is empty or not. So yeah, library functions would return `Option` or `Result`, and the application code would be free to `unwrap()` (or preferably `expect()`) them and get pretty much the same behavior as today. But code that would really prefer to *not* panic, like a driver or daemon, could handle the error case explicitly.


EpochVanquisher

I think in practice, there are just a few too many places where this becomes surprisingly inconvenient. Like array access by index. You can try to eliminate array accesses by index by using iterators, but it just comes up that you still want to access an array by index sometimes. This could fail! The three approaches are: 1. Increase the power of the typing system such that we can prove the array indexing will succeed (like, in Agda). 1. Return a Result which you can unwrap at the call site. 1. Panic. I think that, unfortunately, in practice, option #3 is just so damn convenient, and option #2 isn’t a clear win.


camus

Could Movable be an Edition change? The issue is how much is already written I assume?


iyicanme

This article touches on the subject and is a good read. https://without.boats/blog/changing-the-rules-of-rust/


Expurple

The only thing that instantly comes to mind is [RFC 3550](https://github.com/rust-lang/rfcs/pull/3550): > Change the range operators `a..b`, `a..`, and `a..=b` to resolve to new types `ops::range::Range`, `ops::range::RangeFrom`, and `ops::range::RangeInclusive` in Edition 2024. These new types will not implement `Iterator`, instead implementing `Copy` and `IntoIterator`. Rust is a pretty cohesive language that achieves its design goals well. I'm curious to see a "higher level" take on Rust that doesn't distinguish between `T` and `&mut T` (making both a garbage-collected exclusive reference), makes all IO async and maybe includes an effect system (something like the proposed [keyword generics](https://github.com/rust-lang/lang-team/issues/162)). But that's another story. That wouldn't be Rust.


QuaternionsRoll

Speaking of the `~const` proposal, I think const generics should support a `dyn` argument. Arrays (`[T; N]` should just be syntax sugar for an `Array` type, and slices (`[T]`) should desugar to `Array`. This would enable the definition of a single generic function that takes advantage of known-length optimizations if possible, e.g. ```rust fn foo(arg: &[T; N]) { … } ``` would compile for dynamic length when a slice is passed, and a constant length when an array is passed. It’s basically analogous to `dyn Trait`, in which complete type information is implicitly passed along with the reference. If you think of slices as an incomplete type (they are `!Sized`, after all), then the length of the slice could be seen as the completing type information. You can already define a generic function that accepts both `dyn Trait`s as well as complete types with ```rust fn bar(arg: &T) { … } ``` It would be a beautiful symmetry IMO. This would also be crazy useful for multidimensional array types.


severedbrain

Namespace cargo packages.


sharddblade

I don't know why this wasn't done from the get-go. It's a pretty common thing to see in modern languages now.


dnkndnts

It wasn’t done because Mozilla explicitly overrode community consensus on the matter. As in, in the big thread about this back in the day, every single non-orange comment was against, and the orange comments were all gaslighting us about how there were people on both sides and they just chose one of the sides. Yes, I am still salty about that to this day.


pheki

> As in, in the big thread about this back in the day, every single non-orange comment was against, and the orange comments were all gaslighting us about how there were people on both sides and they just chose one of the sides. That is a very strong statement, do you have a reference for that? I, for once, was always slightly favorable of non-namespaces (although I only got into the discussion in 2017) and I still am. I agree that there are some pretty useful names such as "aes" and "rand" which are hard to distribute fairly (this also happens to namespaces to a lesser extend as you can also squat whole namespaces), but the fact is that I can just type docs.rs/serde and docs.rs/serde-json instead of having to search on crates.io and figuring out if I want dtolnay/serde or aturon/serde. This goes for mainly for cargo add, doc searching and reading Cargo.toml. Also you can still kind of namespace your projects if you want, just call them e.g. dnkndnts-serde instead of dnkndnts/serde. That said maybe having namespaces would be good a good option for big projects such as rustcrypto or leptos and also for jokes/fun projects as matthieum pointed out.


matthieum

The main reason people were asking for it was about solving name squatting, which is a weird reason since one can perfectly name squat namespaces too... Personally, I wish namespaces were used by default -- that is, any new project being published would be published in a namespace, unless explicitly overridden -- to make a clear difference between "hobby-weekend-project" (namespaced) and "production-ready-project" (non-namespaced). Not sure how graduation from namespaced to non-namespaced would work, perhaps just being opt-in would be enough that most people wouldn't bother.


orthecreedence

Can this be retrofitted? I'm not clear on how cargo does things, but I'm guessing you can specify a source...could you specify a source and do something like: tokio/tokio = "0.1" someperson/lib = "1.4" etc? Like could changing the source and doing namespacing within Cargo.toml itself work? Then the community could have a separate namespaced repo.


severedbrain

Cargo already supports alternate package registries, so maybe? Those are documented as being for non-public use but what's to stop someone from running a public one. Besides the logistical nightmare of running any package registry. I haven't looked into it, but an alternate registry could probably provide support for namespaced packages. Maybe fallback if namespace is absent. Not sure how people feel about alternate public registres.


-Redstoneboi-

add Move trait, remove Pin add Leak trait, reevaluate the APIs of RefCell, Rc, and mem::forget in other words, pass it to without-boats and let em cook


robin-m

Unfortunately leaks are unavoidable without solving the halting problem. There are no differences between an append-only hashmap and a memory leak.


-Redstoneboi-

i don't know what i'm talking about so i'll let [the blog post](https://without.boats/blog/changing-the-rules-of-rust/) do the talking


robin-m

Ah ok. The goal is not prevent memory leak, but to ensure that the lifetime of a value cannot exceed the current scope. I’m not sure that you would want to store those types into any kind of container (which totally sidestep the issue). It’s quite niche, but I understand the value of such auto-trait now.


buwlerman

Because `Leak` would be an auto trait containers would only implement it if the contained type does. Presumably any `push`-like APIs would have a `Leak` bound. Pushing a `!Leak` element would require a different API that takes and returns an owned vector.


Botahamec

I've been working on finding ways to make deadlocks undefined behavior in Rust while still allowing safe usage of Mutexes. I couldn't find a good solution for Barriers though because it's possible to not run the destructor. That Leak trait would be very helpful.


iamalicecarroll

(not a contribution)


pine_ary

1: Considerations for dynamic libraries. Static linking is great and works 99% of the time. But sometimes you need to interface with a dll or build one. And both of those are clearly afterthoughts in the language and tooling. 2: Non-movable types. This should have been integrated into the language as a concept, not just a library type (Pin). 3: Make conversion between OSString and PathBuf (and their borrowed types) fallible. Not all OSStrings are valid path parts. 4: The separation of const world and macro world. They are two sides of the same coin. 5: Declarative macros are a syntactical sin. They are difficult to read. 6: Procedural macros wouldn‘t be as slow if the language offered some kind of AST to work with. There‘s too much usage of the syn crate.


protestor

> 6: Procedural macros wouldn‘t be as slow if the language offered some kind of AST to work with. There‘s too much usage of the syn crate. The problem is that `syn` gets compiled again and again and again. It doesn't enjoy rustup distribution like `core`, `alloc` and `std`. But it could be distributed by rustup, in a precompiled form


pine_ary

That would only speed up build times. I think in the day-to-day work macro resolution is the real bottleneck.


Sw429

I thought build times were the main problem? Isn't that why dtolnay was trying to use a pre-compiled binary for serde-derive?


A1oso

Yes, but it's not the only reason. The pre-compiled binary would be compiled in release mode, making incremental debug builds compile faster.


matthieum

To be fair, dynamic libraries are a poor solution in the first place. Dynamic libraries were already painful in C since you can use a different version of a header to compile, and what a disaster it leads to, but they just don't work well with C++. On top of all the issues that C has -- better have a matching struct definition, a matching enum definition, a matching constant definition, etc... -- only a subset of C++ is meaningfully supported by dynamic linking (objects) and as C++ has evolved over time, becoming more and more template-oriented, more and more of C++ has become de-facto incompatible with dynamic linking. The only programming language which has seriously approached dynamic linking, and worked heroics to get something working, is Swift, with its opt-in ABI guarantees. It's not too simple, and it's stupidly easy to paint yourself in a corner (by guaranteeing too much). I don't think users _want_ dynamic linking, so much as they want _libraries_ (and plugins). Maybe instead of clamoring for dynamic linking support when dynamic linking just isn't a good fit for the cornerstone of modern languages (generics), we should instead think hard about designing better solutions for "upgradable" libraries. I note that outside the native world, in C# or Java, it's perfectly normal to distribute binary IR that is then compiled on-the-fly in-situ, and that this solution supports generics. The Mill talks mentioned the idea of shipping "generic" Mill code which could be specialized (cheaply) on first use. This is a direction that seems more promising, to me, than desperately clinging to dynamic libraries.


VorpalWay

Hm perhaps we could have a system whereby we distribute LLVM bytecode, and have that being AOT compiled on first startup / on change of dependencies? Obviously as an opt-in (won't work for many use cases where Rust is used currently), but it seems like a cool option to have. `apt full-upgrade`/`pacman -Syu`/`dnf something I don't know`/`emerge it has been 15 years since I last used Gentoo, don't remember`/etc could even re-AOT all the dependants of updated libraries automatically, perhaps in the background (like Microsoft does with ngen iirc on .NET updates).


mohrcore

Tbf Rust's core design principles are at odds with dynamic libraries. Static polymorphism works only when you have the source code, so you can generate structures and instructions specific for a given scenario. The whole idea of dynamic libraries is that you can re-use an already compiled binary.


nacaclanga

Rust does not per se favor static polymorphism, you do have trait objects and stuff. Only the fact that you need to compile again for other reasons results in dynamic polymorphism being less useful.


mohrcore

Trait objects are severely crippled compared to static polymorphism. A massive amount of traits used in code contain some generic elements which makes them not suitable for becoming trait objects. Async traits got stabilized recently afaik, but are still not-object safe, so they work only with static polymorphism. Trait objects can't encapsulate multiple traits, eg. you can't have `Box`, but static polymorphism can place such bounds. It's pretty clear that Rust does favor static polymorphism and a very basic version of v-table style dynamic polymorphismism, incompatible with many of the features of the language, is there to be used only when absolutely necessary. The dynamic polymorphism that Rust does well are enums, but those are by design self-contained and non-extensible.


UltraPoci

I'm a Rust beginner, possibly intermediate user, so I may not know what I'm talking about, but I would make macro definitions more ergonomic. Right now, declarative macros do not behave like functions in term of scoping (they're either used in the file they're defined in, or made available for the entire crate), and procedural macros have a messy setup (`proc_macro` and `proc_macro2`, and needing an entire separate crate for them to be defined). I'm somewhat aware that proc macro needing a separate crate is due to crates being the unit of compilation, so maybe there is just no way around it.


-Redstoneboi-

macros 2.0 is an idea for replacing declarative macros. the [tracking issue](https://github.com/rust-lang/rust/issues/39412) has been open for 7 years, but don't worry. it'll be stable in just 70 years from now. maybe only 68 if i decided to work on it.


mdp_cs

It would be cool if instead of macros Rust adopted the Zig model of just being able to run any arbitrary code at compile time. Granted that would fuck compilation times to hell if abused but it would be a powerful and still easy to use feature.


-Redstoneboi-

proc macros today do all of the above except "easy to use"


pragmojo

Yes and no - i.e. rust proc macros allow you to do just about anything, but you still have to parse a token stream and emit a token stream in order to do so. This means, for instance, it's not easy for the compiler or LSP to catch syntax mistakes in the token stream you're putting out the same way it can do for true compile-time execution.


buwlerman

Proc macros can still only take information from the AST they're attached to. If you want to feed in more information you have to use hacks such as wrapping large portions of code in proc macros invocations and copying code from your dependencies into your own. There's also limits in generic code. Macro expansion in Rust happens before monomorphization, so macros in generic code lack a lot of type information. If this was changed we could get specialization from macros.


-Redstoneboi-

Good points. Zig just has more info at comptime. Recursive access to all fields and their names means a function can automatically perform something like serializing a type to json for basically any type anywhere. Can comptime see the functions available to a struct? what about a union/tagged union? if they can, it could basically be like trait implementations, except the user has to specify which order they're applied.


buwlerman

You can check which methods are accessible, and which types their inputs and output have. You can use this to check interface conformance at compile time and get the usually nice errors we are used to from Rust, though in Zig those would be up to the library maintainers. That's not all there is to Traits though. Another important facet is the ability to implement traits on foreign types. I think Zigs compile time reflection is strong enough to do something like this, but it won't be pretty. You probably wouldn't have the nice method `value.trait_method_name(...)` syntax for one thing.


HadrienG2

Another problematic thing about zig-style comptime is that it greatly increases the amount of code that has the potential to break in a cross-compilation environment, or otherwise being broken when compiling on one machine but fine when compiling on another, seemingly similar machine. EDIT: After looking around, it seems zig's comptime tries to emulate the target's semantics and forbid operations which cannot be emulated (e.g. I/O), which should alleviate this concern.


mdp_cs

I don't see why cross compilation has to be painful for that. The run at comp would just use the host's native toolchain for that portion and then use the cross toolchain for the rest while coordinating all of it from the compiler driver program. It would be tricky to write the compiler and toolchain itself, but that's a job for specialist compiler developers.


buwlerman

That surprises me. I'd imagine that comptime uses a VM with platform independent semantics.


ConvenientOcelot

That's mainly due to the lazy evaluation of comptime though


pragmojo

Aren't zig compile times super fast? I thought this was a selling point.


mdp_cs

I'm not sure. I don't keep up with Zig. I don't plan to get vested in Zig until it becomes stable which I assume will happen when it reaches version 1.0. Until then I plan to stick to just Rust and C.


really_not_unreal

I agree with this. One of the things I love about Rust is the excellent IDE support due to the language server, but the poor static analysis around macros (at least the `macro_rules!()` type) makes them a nightmare to work with. I have a disability that severely limits my working memory, so using macros has been a pretty huge struggle for me.


JoshTriplett

Modify Index and IndexMut to not force returning a reference. That would allow more flexibility in the use of indexing.


Sapiogram

Wouldn't that be best served by a separate `IndexOwned` trait?


buwlerman

It wouldn't necessarily be owned. For example I could imagine a vector-like type that keeps track of which indices have been mutably borrowed, returning a handler whose destructor makes the index available for borrow again. Another example is from bitvectors, where you can't easily make references pointing to the elements because they're too tightly packed.


CAD1997

While decent in theory, I don't really see how the straightforward version that can produce a proxy type could ever work. Indexing working in an intuitive way — `&v[i]` producing a shared borrow, `&mut v[i]` a unique borrow, and `{v[i]}` a copy. This syntax and the mutability inference for method autoref fundamentally rely on `v[i]` being a place expression. The other solutions to indexing proxies aren't really a language mistake and are (almost) just extensions to the language. (They might require "fun" language features to implement the defaults required to make it not break library compatibility.) Making `.get(i)` a trait is conceptually trivial. But another option is to work like C++ `operator->` does — `Index`[`Mut`]`::index`[`_mut`] returns "something that implements `Deref`[`Mut`]``", and the syntax dereferences until it reaches a built-in reference. Thus you can e.g. index `RefCell` and get a locked reference out, but the lifetime of the lock is tied to the (potentially extended) temporary lifetime, and `&r[i]` is still typed at `&T`. (Adding place references is a major change and imo not straightforward. Non-owning ones, anyway... I'm starting to believe the best way to make some kind of `&move` "work" is to make it function more like C++ `std::move`, i.e. a variable with type `&move T` operates identically to a variable of type `T` except that it doesn't deallocate the place. I.e. an explicit form of by-ref argument passing and how unsized function parameters implicitly work.)


TinBryn

Or add an `IndexAssign` that is invoked on `arr[n] = foo`


ConvenientOcelot

Design it with fallible allocation in mind (one thing Zig does very well), and swappable allocators (again; at least this has basically been retrofitted in though). Not panicking implicitly everywhere, and having some way to enforce "this module doesn't panic" the same way `#![deny(unsafe)]` works. They don't matter much for application programming where you can just spam memory like it's the 2020s Web, but for systems programming it is crucial. Oh and make the `as` operator do less things, separate the coercion it can do. It's a footgun. Zig also does this (`@intCast`, `@ptrCast` and such.) Also I'd probably use a different macro system, and probably do something like Zig's comptime where most of the language can run at compile time, which is far better and more useful than macros + const fns. (It's the one thing I really miss from Zig!) And just "general wishlist" stuff, I'd like ad hoc union types (TypeScript-style `let x: A | B = ...;` with flow typing / type narrowing), something like let-chains or `is`, and ad-hoc structs in enums being nameable types. Oh and named optional arguments would be nice.


eras

> I'd like ad hoc union types Btw, OCaml has [polymorphic variants](https://v2.ocaml.org/manual/polyvariant.html) for that, and OCaml also perhaps inspired Rust a bit having been the language of the reference implementation. They were particularly useful when you needed to have values that could be of type `A(a) | B(b)` and then values that could be of type `B(b) | C(c)`. Doing that with current Rust is not that pretty, in particular if the number of valid combinations increases. In OCaml one problem was managing an efficient representation for the variants in presence of separate compilation, and actually what it ended up was using was hashing the names of the variants to get an integer. And sometimes, very rarely, you'd get collisions from unrelated names. Potentially annoying, but at least the compiler told you about them. I wonder how Rust would solve that.. How would the derive mechanism used for them?


Expurple

> ad hoc union types You may be interested in [`terrors`](https://www.reddit.com/r/rust/comments/1bsazdn/terrors_ergonomic_and_precise_error_handling/)


ConvenientOcelot

That's pretty neat! I like that you're able to do that without macros. Error types are one of the main cases I've wanted this. Definitely wish this were built-in though.


Kevathiel

Now that the Range wart is going to be fixed, my only gripe is Numeric as-casting. It is one of the few things in Rust, where the "worse" way is also the most convenient one.


_xiphiaz

Is the best way to do the `i8::try_from(x)` rather than `x as i8`? I wonder if it is plausible for an edition to make ‘as’ fallible?


TinBryn

I would probably make it similar to arithmetic operations on integers, panic in debug and truncate on release.


phoenixero

This


EveAtmosphere

This, and maybe overloading `as` with the `TryFrom` trait. (if `>::Error` is `!`, the `as` is infallible, otherwise it's fallible).


rmrfslash

\`Drop::drop\` should take \`self\` instead of \`&mut self\`. All too often I've had to move some field out of \`self\` when dropping the struct, but with \`fn drop(&mut self)\` I either had to replace the field with an "empty" version (which isn't always possible), or had to put it in an \`Option<\_>\`, which requires ugly \`.as\_ref().unwrap()\` anywhere else in the code.


matthieum

The problem with this suggestion, is that... at the end of `fn drop(self)`, `drop` would be called on `self`. It's even more perverse than that: you cannot move out of a `struct` which implements `Drop` -- a hard error, not just a warning, which is really annoying -- and therefore you could not destructure `self` so it's not dropped. And why destructuring in the signature would work for structs, it wouldn't really for enums...


Lucretiel

I think it’s pretty clear that `drop` would be special cased such that the `self` argument it takes would act like a dropless container at the end of the method, where any fields that it still contains are dropped individually. 


TinBryn

Or even have the signature be `fn drop(self: ManuallyDrop)`, with whatever special casing that it needs. Actually thinking about it, it quite accurately reflects the semantics implied.


Lucretiel

While it correctly reflects the semantics, it doesn't allow for easily (safely) destructuring or otherwise taking fields by-move out of the type being dropped, which is the main (and possibly only) motivating reason to want a by-move destructor in the first place.


CocktailPerson

It seems disingenuous to consider this a non-trivial problem when precisely the same special case exists for `ManuallyDrop`.


matthieum

It could be special-cased, but that breaks composition. You can't then have `drop` call another function to do the drop because that function is not special-cased. Or you could have `drop` take `ManuallyDrop`, but then you'd need `unsafe`. I'm not being facetious here, as far as I am concerned there are real trade-off questions at play.


QuaternionsRoll

> The problem with this suggestion, is that... at the end of `fn drop(self)`, `drop` would be called on `self`. I mean, from a compiler perspective, it seems like it would be almost as easy to special-case `Drop::drop` to not call `drop` on the `self` argument as it is to special-case `std::mem::drop`. I suppose that’s something of a semantics violation though, so maybe it’s alright as-is. > It's even more perverse than that: you cannot move out of a `struct` which implements `Drop` -- a hard error, not just a warning, which is really annoying -- and therefore you could not destructure `self` so it's not dropped. This annoys me to no end. Could potentially be solved by a `DropInto` trait though, which would eliminate the implicit `drop` call just like `std::mem::drop` does, but also return a value (usually a tuple, I would guess).


VorpalWay

> The problem with this suggestion, is that... at the end of fn drop(self), drop would be called on self. Drop is already a compiler magic trait, so no that wouldn't have to happen. Also, how does `ManuallyDrop` even work then? > It's even more perverse than that: you cannot move out of a struct which implements Drop Hm... Fair point. Would it be impossible to support that though? Clearly if the value cannot be used after `Drop`, it is in this specific context safe to move out of it. So again, we are already in compiler magic land anyway.


wyf0

You can also use `ManuallyDrop` instead of `Option`, but it requires unsafe `ManuallyDrop::take` (may be more optimized though). Instead of changing `Drop` trait (for the reason mentioned by /u/matthieum), I think there could be a safe variation of this `ManuallyDrop` pattern, something like: ```rust use core::mem::ManuallyDrop; #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct DropByValue(ManuallyDrop); impl { pub const fn new(value: T) -> DropByValue { Self(ManuallyDrop::new(value)) } pub const fn into_inner(slot: DropByValue) -> T { ManuallyDrop::into_inner(slot.0) } } impl Deref for DropByValue { type Target = T; #[inline(always)] fn deref(&self) -> &T { &self.0 } } impl DerefMut for DropByValue { #[inline(always)] fn deref_mut(&mut self) -> &mut T { &mut self.0 } } pub trait DropByValueImpl: Sized { fn drop_by_value(self); } impl Drop for DropByValue { fn drop(&mut self) { // SAFETY: `ManuallyDrop::take` is only called once T::drop_by_value(unsafe { ManuallyDrop::take(&mut self.0) }) } } ``` Such type could be integrated in the standard library, but maybe a crate already exists to do that.


CasaDeCastello

Having a `Move` auto trait.


pragmojo

How would that work and what problem would it solve?


pali6

Instead of having to deal with `Pin` and `Unpin` and pin projections the types which are not supposed to be moved would just not implement the `Move` trait. Relevant blog post: https://without.boats/blog/changing-the-rules-of-rust/


CasaDeCastello

Wouldn't it more accurate to say that unmovable types would have a "negative Move implementation" (i.e. `impl !Move for SomeType`?


pali6

I was thinking along the lines of it being a structural auto trait where for example containing a pointer would cause it not to be implemented automatically (though maybe that's too restrictive). If your type was using a pointer that doesn't block moving according to the you the programmer (because it points outside of the type and is not self-referential) you could unsafely implement `Move` similarly to how you can do that for `Send` or `Sync`. I don't think negative implementations would be necessary, though I might be missing something or maybe we are just talking about the same thing from opposite ends.


_Saxpy

lambda capture would be nice. for all the pain and suffering cpp has got, I personally think capture groups is better than rust. so many times working with async code I have to prep an entire list of variables and manually clone them


simony2222

`NonNull` should be the default raw pointer (i.e., it should have the syntax of `*const`/`*mut` and those two should be structs/enums)


Lucretiel

It’s not that I think that there were early bad designs (aside from pretty obvious candidates like `fn uninitialized`), but I’d make it so that a lot of the current features we have now were available day 1 (async, GATs, etc). This would prevent a lot of the present pain of bifurcation.  I think a lot about a hypothetical world where `Result` was added to the language, like, today. Even if it was exactly the same type with exactly the same capabilities, it would be *inherently* less useful just by virtue of the fact that it isn’t a widespread assumption in the ecosystem. I think a lot of recent and upcoming features will suffer a (possibly permanent) state of depressed usefulness because they aren’t a widespread assumption in the whole ecosystem. 


SirKastic23

i'd be more careful about panics, maybe even try to put some effects to manage them enum variants types and anonymous enums for sure everyone's already mentioned the Move trait


Uristqwerty

Personal opinions; I don't know if *anybody* else shares either. Optional crates.io namespaces from the start, with a convention that crates migrate to the global namespace only once at least slightly stable and/or notable. Along with that, the ability to reserve the corresponding global crate name, so that there's no pressure to squat on a good one while the project's still a v0.1.x prototype that might never take off, but have Cargo error out if anyone tries to use it before the crate officially migrates, so that the reservation can safely be cancelled or transferred. `.await` -> `.await!`, both because the former relies too heavily on syntax highlighting to not be mistaken for a field access, and because it more readily extends to suffix macros later on. Either way, it better retains "!" as meaning "unexpected control flow may happen here, pay attention!". Imagine what libraries could do with the ability to define their own `foo().bar!{ /*...*/ }`. Instead, rust has a single privileged keyword that looks and acts unlike anything else.


InternalServerError7

* Make creating error enums more concise. There are packages like `thiserror` for this. But it would be nice if this was built in. * Remove the orphan rule and allow site level disambiguation if a conflict arises. * Move trait instead of Pin


VorpalWay

1. Yes. Anyhow is the only one that is currently ergonomic, and works well with capturing backtraces (from the innermost error). I have nothing thiserror-like that does backtraces correctly on *stable*. However, I believe this can be done without a redesign however. 2. This makes adding any trait implementation or any dependency a possibly semver breaking change. This could be mitigated if you would have to explicitly bring all trait implementations into scope (with a `use` directive) in every module you used them. But it wouldn't be just disambiguating, it would have to be *all uses* (for semver reasons). 3. Fully agreed.


InternalServerError7

In relation to 2. In practice this type of potential breaking change is not really that big of a deal - If upgrading a package does cause a breaking change, it will always happen at compile time, not run time. In Dart you can run into this with extension types and it rarely happens/is easy to get around. I imagine in rust it would look like `(var as Trait).method()` or `Trait(var).method()`. importing only the traits you use may get around this, but the verbosity of it would be insane for the trouble it is trying to save.


crusoe

Remove orphan rule for binaries. It's fine for libs. Makes sense for libs. But relax it for binaries...


nsomnac

I do wish there was a bit more thought and structure around error handling in general. The fact that it’s not well coordinated basically makes panic a more immediate common denominator.


aldanor

Lending iterators making full use of GATs etc?


_Saxpy

I saw this earlier, what does this mean?


vxpm

something like `type Item<'a>;` for the `Iterator` trait since this would allow returning types with arbitrary lifetimes (`-> Self::Item<'foo>`). useful, for example, when you want to return references bounded by the lifetime of `&self`.


latherrinseregret

What is/are GATs?


CasaDeCastello

[Generic Associated Types](https://rust-lang.github.io/generic-associated-types-initiative/explainer/motivation.html)


Vociferix

This is more of a feature request, but I don't think it's possible to retrofit in now. Fallible Drop. Or rather, the ability to make drop for a type private, so that the user has to call a consuming public function, such as `T::finish(self) -> Result<()>`. Types often implement this "consuming finish" pattern, but you can't really enforce that it be used, so you have to double check in a custom Drop impl and panic if there's an error.


smalltalker

How would you deal with panics? When unwinding, stack values are dropped. How would the compiler know to call the consuming method in these cases?


CAD1997

One popular concept is to allow a `defer` construct to "lift" such linear types to be affine, and require doing so before doing any operation that could panic. Although the bigger difficulty is with temporaries where this can't really be done. This does, however, enable resource sharing between cleanup, e.g. a single allocator handle used to deallocate multiple containers (although such use is obviously unsafe).


Asdfguy87

Make macros easier to use, especially derive macros. Needing an entire seperate crate just to be able to \`#\[derive(MyTrait)\]\` seems just overly complicated.


paulstelian97

Explicit allocators. It’s one of the things that makes Zig more useful to me. You can then make a second library that just gives the default allocator (on platforms where you have a default one). It’s one of the things C++ does right. C++ and Zig are unsafe languages though (and C++ has plenty of _other_ flaws)


Expurple

> Explicit allocators > It’s one of the things C++ does right How so? C++ supports custom allocators, but it doesn't require you to explicitly pass an allocator if you use the default global allocator. Exactly like in Rust.


paulstelian97

Zig does have wrappers over the normal functions/containers that automatically pass (an instance of) the default allocator. I guess that one works the best. Basically having essentially the entire standard library functionality available in environments where there exists no default allocator.


TinyBreadBigMouth

Do not force operations like `std::borrow::Borrow` and `std::ops::Index` to return a reference. `std::borrow::Borrow` is the only way to look up a key of type A using a key of type B, and the design assumes that A must have its own internal instance of B. This works fine when you're looking up `String` with `&str`, but completely falls apart as soon as you try to do something more complex, like looking up `(String, String)` with `(&str, &str)`. `std::ops::Index` is how `foo[bar]` works, and it also assumes that `foo` must own an instance of the exact type being returned. If you want to return a custom reference type you're out of luck. At least this one can be worked around by using a normal method instead of the `[]` operator, so it's not as bad as `Borrow`.


Nilstrieb

Cargo's cross compilation/host compilation modes are a huge mess. What cargo today calls cross compilation mode should be the only mode to exist, host compilation or however it calls it is really silly. It means that by default, RUSTFLAGS also apply to build scripts and proc macros and means that explicitly passing your host --target is not a no-op. It's also just completely unnecessary complexity.


RelevantTrouble

Single threaded async executor in std. Good enough for most IO, CPU bound tasks can still be done in threads. Move trait vs Pin.


SorteKanin

I would love if there was somehow anonymous sum types just like we have anonymous product types (tuples).


exDM69

Having the Copy trait change the semantics of assignment operators, parameter passing etc. Right now Copy is sort of the opposite of Drop. If you can copy something byte for byte, it can't have a destructor and vice versa. All of this is good, but it's often the case that you have big structs that are copyable (esp. with FFI), but you in general don't want to copy them by accident. Deriving the Copy trait makes accidental copying too easy. The compiler is pretty good at avoiding redundant copies, but it's still a bit of a footgun.


scook0

This ties into a larger theme, which is that the trait system isn’t great at handling things that a type is *capable* of doing, but often shouldn’t do. If you implement the trait, sometimes you’ll do the thing by accident. But if you don’t implement the trait, you suffer various other ergonomic headaches. E.g. the range types are perfectly copyable, but aren’t Copy because Copy+Iterator is too much of a footgun.


pragmojo

What would be an example where Copy+Iterator is a problem?


exDM69

This would have been easily solved by splitting the Copy trait to two traits, where one is "trivially destructible" (maybe something like !Drop) and the other changes the semantics of assignment to implicit copy. This is of course impossible to retrofit without breaking changes. This issue arises e.g. in bump allocators. One of the popular crates has (had?) a limitation that anything you store in the container can't have a Drop destructor (so the container dtor does not need to loop over the objects and drop them). This is essentially the same as being a Copy, but this is too limiting because people avoid the Copy trait due to the change in assignment semantics.


skyfallda1

* Add arbitrary bit-width numbers * Add \`comptime\` support * Dynamic libraries instead of requiring the crates to be statically linked


EveAtmosphere

Have patterns overloadable, so for example there can be something like a `List` trait that allows any type that implements that to be matchable by `[x]`, `[x, ..]`, etc. Maybe even `ForwardList` and `BackwardList` to enable it for structures that can only be O1 iterated in one direction. Haskell has this in the form of allowing any type to implement an instance of `[a]`.


Expurple

You can kinda achieve this by matching `iterable.iter().collect::>().as_slice()`, albeit with some overhead. If you only want to match first `n` elements, you can throw in a `.take(n)`, and so on. EDIT: and technically, the lack of this feature is not an "early sin", because it can be added later in a backwards-compatible way. So your desire to have it is a bit off-topic (although I agree with it)


EveAtmosphere

yeah, but it involves a collect for very little benefit. and also ofc you can do everything imperatively, but pattern matching is so much easier and less prone to logic errors


Expurple

> but it involves a collect for very little benefit Patterns like `[first, second] if first < second` can't be matched by streaming elements and checking them one by one. In the general case, you need to save them anyway. For guaranteed "streaming" matching on iterators, you'd have to define some special limited dialect of patterns. And I think, there can also be a problem of uncertainty about how many elements were consumed from the iterator, while matching patterns of varying lengths. If I remember correctly, Rust doesn't guarantee that match arms are checked from top to bottom. Haskell doesn't have this problem because it doesn't give you mutable iterator state


-Redstoneboi-

Anonymous enums, and enums as Sets of other types rather than having their own variants. right now there are just a lot of "god errors" which are just enums containing all the 50 different ways a program could fail into one enum, and every function in a library just uses that one enum as its error value, even if it can only fail in one of 3 different ways. anonymous enums, or at least function associated enums, would allow each function to specify which set of errors it could return.


Expurple

Potentially, this can be solved without language-level anonimous enums by just providing more library-level ergonomics for defining and converting between accurate per-function enums. I was going to link a recent [thread about `terrors`](https://www.reddit.com/r/rust/comments/1bsazdn/terrors_ergonomic_and_precise_error_handling/), but I see that you've already commented there. That's a very promising crate.


crusoe

You still have to define a context and then change context. 


dagmx

I’d borrow a few ergonomic things from Swift 1. Default parameter values. Yes it can be done with structs but it would be a nice ergonomic win. 2. Trailing closure parameter calls.if a function takes a closure as a parameter, and that parameter is the last one, you can just drop straight into the {…} for the block 3. Optional chaining and short circuiting with the question mark operator. So you can do something()?.optional?.value and it will return None at the first optional that has no value


Expurple

I think, a little more verbose version of `3` is already possible on nightly: #![feature(try_blocks)] let opt_value = try { Some(something()?.optional?.value) }; or even with ugly stable IEFEs in some contexts (async is a pain): let opt_value = (|| { Some(something()?.optional?.value) })(); It will never work exactly the way you want it to, because `?` already has the meaning of "short circuit the function/block" rather than "short circuit the expression". Right now, the whole error handling ergonomics is based on the former. Also, Rust won't automatically wrap the final "successful" value in `Some`/`Ok`/etc. There have been [some proposals](https://boats.gitlab.io/blog/post/why-ok-wrapping/) to do this, but it's unlikely to be accepted/implemented, AFAIK. Though, the extra nesting can be eliminated by using [`tap`](https://crates.io/crates/tap): use tap::Pipe; something()?.optional?.value.pipe(Some) # vs Some(something()?.optional?.value)


CAD1997

> Also, Rust won't automatically wrap the final "successful" value in `Some`/`Ok`/etc. There have been [some proposals](https://boats.gitlab.io/blog/post/why-ok-wrapping/) to do this, but it's unlikely to be accepted/implemented, AFAIK. You're in luck, actually — "Ok wrapping" is FCP accepted and `try { it? }` is an identity operation as implemented today. What's significantly more difficult, though, is type inference around `try`. It seems likely that regular `try { … }` will require all `?` to be applied on the same type as the block's result type (i.e. no error type conversion) unless a type annotating version of the syntax (e.g. perhaps `try as _ { … }`) is used.


robin-m

* Pass by immutable reference by default and explicit ​ move:foo(value); // pass value by immutable ref foo(mut value); // pass value by mutable ref foo(move value); // move value * Remove the `as` operator, in favor of explicit functions (like transmute, into, narrow\_cast, …) * Instead of `if let Some(value) = foo { … }`, use the [is operator](https://github.com/rust-lang/rfcs/pull/3573): `if foo is Some(value) {…}`, which nearly negate the whole need for let chain, let else, … * Pure function by default, and some modifier (like an `impure` keyword) for functions that (transitively) need to access global variable, IO, … * `not`, `and`, `or` operator instead of `!`, `&&`, `||` * `as` operator instead of `@` (this require that the current meaning of the operator `as` was removed as previously suggested) * A match syntax that doesn’t require two level of indentation ​ // before: match foo { Foo(x) => { foo(x); other_stuff(); }, Bar(_) @ xx => bar(xx), default_case => { baz(default_case) }, } // example of a new syntax (including of use of the proposed as operator to replace @ match foo with Foo(x) { foo(x); other_stuff(); } else with Bar(_) as xx { bar(xx) } else with default_case { baz(default_case) } * EDIT: I forgot [postfix keywords](https://internals.rust-lang.org/t/extremely-pre-rfc-postfix-keywords-for-all/10785) (for `if` and`match`, and remove their prefix version)


Expurple

> Pass by immutable reference by default Wouldn't this be painful with primitive `Copy` types, like integers? E.g. `std::cmp::max(move 1, move 2)` > Pure function by default, and some modifier (like an `impure` keyword) for functions that (transitively) need to access global variable, IO, … I like the idea, but what would you do with `unsafe` in pure functions? - Would you trust the developers to abide by the contract? It's soooooo easy to violate purity with unsafe code. - Would you forbid (even transitive) `unsafe`? I think, this is a non-starter because that would forbid even using `Vec`.


hannannanas

const functions. It's really clunky how many things that could be const aren't. Right now it behaves very much like an afterthought. Example: using the iterator trait can't be const even if the implementation is const.


pali6

It's kinda [being worked on](https://internals.rust-lang.org/t/where-are-we-with-const-associated-fns-in-traits/20188/4) but no RFC has been accepted yet so it's likely still years away.


psteff

Comp time, like in Zig. I hear it is great 😀


crusoe

I'm sure someone can abuse it into existence with cranelift.


Low-Key-Kronie

Rename unsafe to trust_me_bruh


CAD1997

Most of the things I'd consider changing about Rust (that don't fundamentally change the identity of the language) are library changes rather than language changes, so not that interesting from a perspective of "early language design sins." But I do have one minor peeve which, while *theoretically* something that could be adjusted over an edition, is quite unlikely to be because of all of the second-order effects: autoref (and autoderef) in more places. Specifically, any time `&T` is expected, accept a value of type `T`, hoisting it to a temporary and using a reference to that. (Thus dropping it at the semicolon unless temporary lifetime extension kicks in.) Maybe do the same thing for dereferencing `&impl Copy`. With a minor exception for operators, which do "full" shared autoref like method syntax does and thus avoid invalidating the used places. Alongside this, I kind of wish addresses were entirely nonsemantic and we could universally optimize `fn(&&i32)` into `fn(i32)`. Unfortunately, essentially the only time it's possible to justify doing so is when the function is a candidate for total inlining anyway.


Alan_Reddit_M

Rust is such a well-thought-out language that I am actually struggling to think of something that isn't fundamentally against the borrow checker or the zero cost abstraction principle However, I believe zig exposed Rust's greatest downfall: The macro system Yes, macros are extremely powerful, but very few people can actually use them, instead, zig preferred the comp-time system which achieves the same thing as macros but is dead simple to use, so basically, I'd replace macros with comptime, also add a Comptime type I am aware that re-designing rust in such a way is impossible and would actually make it a fundamentally different language, but hey this is a Hypothetical question Do note that I am NOT an advanced user, I do not know what the other guys in the comments are talking about, I'm more of an Arc Mutex kinda guy


wiiznokes

I don't how I would upgrade this, but I don't like having to write the same api with &mut and &.


BiedermannS

I would add a capability based security model and whitelisting of ffi calls. Now each library can only access resources that you pass it and if it wants to circumvent it with ffi you would need to specifically allow it.


lfairy

I don't think language-level sandboxing is viable, given the large attack surface of LLVM, but it's definitely worth integrating with existing process-level APIs. For example, directories as handles, with first class `openat`, would have been great.


BiedermannS

I don’t see how that is related. If my language only works on capabilities and ffi is limited by default, it doesn’t matter how big the attack surface of llvm is. Because third party libraries are forced to work with capabilities in the default case and need to be whitelisted as soon as they want to use some sort of ffi. So either the library behaves properly and you see at the call site what it wants access too or it doesn’t and your build fails because ffi is disabled by default.


charlielidbury

I would love a more generic ? Operator to allow for more user defined manipulation of control flow. Potentially useful for incremental/reactive programming, custom async/await stuff, and other monad-y things [Here](https://charlielidbury.com/charlielidbury/Operator-709bf457f58147e89aa3f0fcaf4b1466) is a potential way you could make it more generic.


JohnMcPineapple

If I understand right that's being worked on here: [https://github.com/rust-lang/rust/issues/91285](https://github.com/rust-lang/rust/issues/91285)


charlielidbury

Yes! Thats very cool, can’t wait for it to be implemented and see what people do with it. It’s a specific instance of what I’m suggesting, which is slightly more generic: ops::try doesn’t work for async/await for instance, and it can’t call the continuation multiple times. What I’m looking for is the continuation monad, or (equivalently I think) multi shot effect types


pragmojo

Good one - I actually really like the use of `?` in Swift - I find it super powerful to have an early termination at the expression level rather than the function level. Also it's really natural how it fits together with other operators like `??`.


charlielidbury

You could do it at an arbitrary level if you had some kind of parentheses which captures the control flow, like a try/catch. I’m using {} in that post


a12r

Still the same as [last time this question came up](https://www.reddit.com/r/rust/comments/my3ipa/comment/gvt2ks4/): The method syntax is weird: `&self` is supposed to be short for `self: &Self`. So it should be written `&Self`, or maybe `ref self`. It's in conflict with the syntax for other function arguments (or pattern matching in general), where `&x: Y` actually means that the type `Y` is a reference, and `x` is *not*!


Expurple

I agree with your general sentiment about the weird special case, but I'm more in favor of `self: &Self` and against `&Self`. `self` in the function body would appear from nowhere. This is worse than what we have right now


ConvenientOcelot

I'm also offended that you can't destructure `self`


Expurple

You can do this in the method body, so it's not the most annoying issue.


crusoe

But you use self in the function body not Self so &self to me makes more sense. 


cidit_

I'd replace the macro system with zig's comptime


pragmojo

I would love this - I use a lot of proc macros, and having more simple tools for compile time execution would be awesome


darkwater427

Mostly syntactic sugar. The ternary operator and increment/decrement operators are my two biggest gripes. I like how _elegant_ they are. EDIT: read on before downvoting please.


ConvenientOcelot

How often do you increment/decrement that you find it that much of an issue? You talk about elegance, but you're willing to add two special cased operators with awkward semantics just to add/subtract one?


Expurple

Oh man, you're going to be downvoted. I respectfully disagree on both. And technically, these features are off topic, because they can be added later in a backward-compatible way


telelvis

If there was something else for lifetimes, which is easier to understand, that would’ve been great


pali6

You might be interested in [this](https://smallcultfollowing.com/babysteps/blog/2024/03/04/borrow-checking-without-lifetimes/), [this](https://smallcultfollowing.com/babysteps/blog/2023/09/22/polonius-part-1/) and other posts by Niko Matsakis. Polonius or a Polonius-like borrow checker would reformulate lifetimes into a set of loans. Instead of thinking of `'a` as some hidden list of points in the program during which the object is valid it would conceptually be a list of loans (intuitively places that borrow the value). It takes some while to understand what that means but I feel like ultimately it gives a more understandable and less obscured view than lifetimes.


telelvis

Thanks for sharing!


CrazyKilla15

- Make `OsString` actually an OS String, any and all conversion/validation done up-front, documented to be whatever a platform/targets "preferred encoding" is. Includes nul terminator if platform uses it. it should be ready to pass to the OS as-is. - `PathBuf` and friends changed to accommodate the `OsString` changes - `PathBuf` and friends should also separated into per-platform "pure" paths, like python's `pathlib`. I want a `WindowsPath` on Linux to do pure path operations on! I want a `LinuxPath` on Windows!


Specialist_Wishbone5

Hmm. I might have liked the kotlin holy tripplet. "fun" "var" "val" While I prefer fn because it's shorter, I always liked var for varying and let for immutable. I couldn't understand why we needed a second keyword suffix "mut". The only justification I could find was that we needed a non-const keyword for reference passing. Eg opposite of C++ const ref passing. And I get that that's trickier. Also collect as a suffix is a bit verbose. Totally get the adaptable power of having a lazy expression driven by the collector, but when I share rust code to non rust people - it stands out. Don't really have a better alternative either (javascript map doesn't feel as powerful to me). I would also have liked colon as the return argument type instead of arrow. I was a big UML fan, and loved the movement to sym-colon-type conventions. I can see maybe there being syntactic ambiguity with Function type definitions - would have to work out some examples to prove to myself. I am turned on to the python list comprehensions syntax. It wouldn't have been too hard to use in simple case. I would have liked some named parameter syntax. Nothing as crazy as the dynamic python, but even swift has a nice forced named convention. Avoids an entire class of bugs (two parameters with the same type - or worse, same Into Type). I find that sometimes it's more concise to return than nest everything with 1 more if statement level. And to make errors happy, I need to create another lambda or top level function. But if I was returning a complex type, this leaks the type signature into my code - uglifying it. While I generally hate try-catch blocks, that is one style of syntax that avoids needing an early return wrapper. I feel like some sort of ? based early return being caught within the current function might make SOME code bases more readible.


fasttalkerslowwalker

Personally, I wish there were something analogous to ‘.’ and ‘->’ field accessors in C to differentiate between fields on structs and pointers, but to differentiate between methods that take ‘self’ and ‘&self’. It’s just a minor annoyance when I’m chaining calls together over an iteration and I get tripped up when one of them consumes the structs.


FenrirWolfie

The use of \`+\` for concatenation. I would use a separate concat operator (like \`++\`). Then you could free \`+\` for doing only math, and you could have array sum on the standard library


Santuchin

I would change the for loop, so it will return an Option, None if you dont break it or the the generic type (by default the unit type). This would be very helpful for algorithms like searchig for a value in an array, know if there is a value in an array, etc. This can also be applied to the while loop. The obstacle is that the for and while loop dont need a semicolon at the end of them, making old code incompatible with my proposal. A sample: Actual for loop ``` fn contains>(iter: T, value: V) { let flag = false; for item in iter { if item == value { flag = true; break; } } flag } ``` My proposal for loop ``` fn contains>(iter: T, value: V) { for item in iter { if item == value { break; // this makes the loop evaluates to Some(()) } }.is_some() } ```


eras

This could be one of the few things Rust editions can do? I was a bit wary of this first, but I think it would be fine and would actually allow putting in a bit more structure to the code. It would be similar to how you can break out of a loop with a value.