fn list_items<'life0, 'life1, 'async_trait>(
&'life0 self,
collection_href: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<ItemRef>, Error>> + Send + 'async_trait>>
where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Rendered docs: https://mirror.whynothugo.nl/vdirsyncer/v2.0.0-beta0/vstorag...Source: https://git.sr.ht/~whynothugo/vdirsyncer-rs/tree/v2.0.0-beta...
Unrelated to parent but maybe relevant to you: Rust API naming guidelines say no `get_` prefix on getters.
https://rust-lang.github.io/api-guidelines/naming.html#gette...
Not only is is incredibly ugly it's also rather confusing, but it fits well into how I view Rust, complicated for the sake of making the developers look smart.
It wouldn't fit the syntax of the language obviously, but why not simply have the developer prefix the variable with the keyword "lifetime", rather than assigning a symbol. It seems a little like starting functions in Go with a upper case letter to export them, dude just give us an export keyword, it's fine.
Can you clarify why you have that opinion? What would your syntax suggestion have been?
...
>> The use of ' as a symbol that has any meaning on it's own has got to be one of the most stupid choices I've seen in a language
> What would your syntax suggestion have been?
Is the syntax suggestion he provided not applicable?
Lets look at proposed syntax
fn list_items<lifetime life0, lifetime life1, lifetime async_trait>(
&lifetime life0 self,
collection_href: &lifetime life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<ItemRef>, Error>> + Send + async_trait>>
where
Self: life0,
life0: async_trait,
life1: async_trait,
I'm not going to pretend I understood what mrweasel meant fully, so I assume we can either omit generic or in parameter declaration (so I went with omitting lifetime keyword in parameters): fn list_items<lifetime life0, lifetime life1, lifetime async_trait>(
&life0 self,
collection_href: &life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<ItemRef>, Error>> + Send + async_trait>>
where
Self: life0,
life0: async_trait,
life1: async_trait,
I guess you might be able to omit the "generic part" like so (it might be impossible, lifetime are just generics useful for lifetime tracking): fn list_items(
&lifetime life0 self,
collection_href: &lifetime life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<ItemRef>, Error>> + Send + async_trait>>
where
Self: life0,
life0: async_trait,
life1: async_trait,
In both cases, you get a huge verbosity increase, and mix between not knowing if a value like `Self: x` is a trait with lower case or a lifetime.So you trade verbosity for more ambiguity and programmer confusion, and possibly worse error reporting (is &a b a lifetime or a missing comma e.g. &a, b).
Now you could say, we don't close it in contractions in English, so there's a case where one ' can just exist on it's own. That's sort of fine, a bit outside of the realm of programming, but fine, but then I think you should remove then '...' usage. It's really confusing that the same character has two different meanings depending on something that happens later. Rust does this with ! as well if I understand correctly, so it's NOT like everywhere else, but something!() is macro expansion... Why not just prefix with "macro" so macro something()
So you have something that has a ' in front, is that a lifetime, or a missing ' later? The compiler will catch it, so it not a problem in that sense, it just makes it hard to read the code.
Personally I would almost always prefer a keyword. For Rust I think my problem is that the language is a little to happy with symbols and operators being one character picked for the none numbers and letters and the choice of ' makes it seem like they are running out of characters to choose from. Like we just one or two features away from assigning meaning to § and €.
I think you didn't use that many languages to see other forms [1]. LISP and Scheme don't have single-quoted string literals for example. Double-quoted strings are not really universal either, for example SQL uses single-quoted strings and double-quoted names.
[1] https://rigaux.org/language-study/syntax-across-languages.ht...
The ' aren't used in places where strings occur (strings just don't make sense there anyways), don't take up to much space (i.e. give more space to the name).
I am not a Rust pro, but this has never been an issue for me, same for ! for macro expansions.
Not once has this come up for me. They are in completely different places syntactically and can never overlap.
Sure, `'` might be text related in a lot of languages but definitely not universally. In LISP 'foo is shorthand for (quote foo) and also does not have a second character. Ocaml uses 'foo for types and foo' is just a valid identifier. Standard ML also has 'foo for type variables and I believe also allows identifiers named foo'. Haskell allows identifiers named foo' as well.
Maybe it's odd coming from languages you are familiar with, but it's not at all something that is unique to Rust.
> Rust does this with ! as well if I understand correctly
I am not sure how the case with ! is similar. Macros just end with ! to make them clearer visually, it's not part of an operator. There can never be any syntax ambiguity with them, neither visually or lexically. Also what would be the point. Take this example:
try!(do_something(...)).further();
Do you really think this would be more readable? (macro try(do_something(...))).further();Much more readable, I can just browse the code, and see: Hey a macro.
I mostly write Python, but sometimes have to read, debug and modify C, Java, PHP, JavaScript, Bash and so on. Having code being easily readable and obvious is a huge win.
Very often your code has to be read, and reasonably understood by someone who doesn't exactly know the language, or at least is much less proficient. They don't need to be able to do complex task or debug the inner most workings of your code, but they do need to be able to reason about it, quickly, if you want them to contribute with patches or detailed bug reports.
let f = ... and
let f' = ...It's a little less bad, because you can just have your own style guide that says "Don't do that".
Rust doesn't allow multi-letter character literals for the usual reason, so there is no real ambiguity. Some old editors do only have a very-limited syntax highlighter with fixed rules (e.g. no regexp), so such editors will be indeed unable to handle this, but they won't be able to support many other languages if that's the case.
This is not nothing if you ask me. Rust is a language within which your shifty naive program will still outperform many other solutions, all while being reliable as heck if you wield the type system the right way.
The only thing that I dislike about it is that certain code becomes unreadable. As this article says it often becomes unreadable for a reason — a reason which you would just not think about in other languages — but unreadable is still unreadable.
It makes type information unnecessarily longer without adding information, and feels like writing "end_stm" instead of ";" after every line
Some will prefer the short nature of just typing ', where people like me would prefer that you just add a few more characters so it reads more easily.
' itself (!) may not convey information, but it's existence does convey information.
While making it a keyword may help beginners to understand code and wish they could see the meaning directly, because they don't know the meaning of certain symbols yet, people who know the language and productively produce code are more concerned about content rather than syntax.
Content meaning the name of the life time, the name of the types, the name parameters, the structure of the types, the constraints on the types, etc.
Especially for Rust, there is a lot of the things mentioned above, and since the concepts in Rust aren't easy, it's important that these things get as much space as possible from the limited space on the screen, instead of wasting it by self explaining syntax elements, which are trivial to memorize.
Thus { instead of begin_function_body, *; instead of statement_end, ? instead of 'if err != nil then return err' and ' instead of lifetime.
enum foo; blah ...;
edit: -4 points on my parent comment for a totally valid opinion, not violating any commenting guide line (please enlighten me). However, for the down voters: "Please don't use Hacker News for political or ideological battle. That tramples curiosity."
Turning `'` into `lifetime` would make Rust code that needs explicit lifetimes extremely verbose. That would make code harder to keep on the same line, for no added benefit in expressiveness.
Allowing both `'` and `lifetime` in the syntax would make the language more confusing to anyone who is learning it, and it opens up unproductive debates about syntax in code bases if two authors have conflicting preferences.
Of course, mixing keywords single quotes is confusing, but currently the single quote is also confusing.
Just to illustrate: The keyword could just be called lif, which has the same length as dyn.
' has a long history of use for various purposes in programming language syntax not derived from C.
In Ada, attributes are a single quote followed by the attribute name.
If I have an enum called Fruit then Fruit'First would give the first value in the Fruit enum definition.
http://www.ada-auth.org/standards/22rm/html/RM-4-1-4.html
Attributes provide meta information about types and are very useful when working with custom integer types and ranges:
https://learn.adacore.com/courses/advanced-ada/parts/data_ty...
Using ' for Rust lifetimes or Ada attributes is just a sigil https://en.m.wikipedia.org/wiki/Sigil_(computer_programming)
It is not too different from:
& for addresses / references in C, C++, and Rust,
* for dereferencing in C, C++, and Rust
$ for value substitution in shells and scripting languages
: to mark keywords in Clojure and some LispsIn modern languages, we don't have to think about lifetimes most of the time, let alone notate them.
Rust has too much stuff converging into the same context.
https://www.mainframestechhelp.com/tutorials/cobol/arithmetic-statements.htm
"Confusing" is mostly a question of familiarity; "ugly" one of taste. When you're designing a language's syntax, there is a tension between making the language feel recognizable to beginners/non-users and communicating important information saliently to experts. The former errs on the side of least-common-denominator symbols and explicit constructions, while the latter errs on the side of expression density and implicit understanding.Language features that appeal to beginners and outsiders naturally aid in language adoption, even if they actively work against expert practitioners. So, funnily enough, we should a priori expect the zeitgeist opinion to favor lowest-common-denominator languages features and shun high-utility but "complex" ones.
That is a real shame, however. As a business or whatever, instead of maximizing for ease of onboarding, I want to maximize for facility in exploring the end-goal problem domain, i.e. expert work. Instead of picking a "readable" language, I want to pick one that increases our ability to find simple solutions to complex-seeming problems, conventionally readable or not.
IMHO, baseline languages like Python are great for writing down what you already think but terrible for iterating on your understanding, and 95% of our work as engineers is (should be?) changing our understanding of the problem to fit reality as it bumps us in the face.
I have to disagree. I've been working on HashBackup for 15 years now, and believe me, my understanding of backups has grown immensely over those years - with Python. Python may have some things I have to work around, as all computer languages do, but after all this time I still love working on and just reading through the code that has resulted from over 3200 commits.
Python's simple, easy-to-read syntax, encourages me to change things, even in a complex section of code that I haven't looked at in years. For me, Rust's emphasis on multi-character syntax vs keywords makes it visually unappealing and ugly, and I don't think I'd enjoy working on ugly code for 15 years, even if it does run fast. Not intending to start a huge language discussion, but I do think the specific point of Python not being suitable for experts in a problem domain is not true, at least not for me.
type 'a t = 'a option =
| None
| Some of 'a
I think this is the case because IIRC this function: fn slice<'a>(s: &'a str) -> &'a str { &s[0..2] }
Is generic over the lifetime of `s`, and it I assume it would have been difficult to distinguish generic types and lifetimes.Imo, Rust should introduce some syntax like `async(dyn+Send)` that desugars to `Box<dyn Future<Output = bla> + Send>`. This solves most of the async wards if you don't care about heap allocation and perfect performance.
async fn list_items(&self, colletion_href: &str) -> Result<Vec<ItemRef>, Error>Rust can improve this by introducing syntax like `'a contains 'b`
Sure, maybe I don't need to statically guarantee the correct execution of code that could easily just throw at runtime instead, but it sure is a fun hobby.
1) Every time I go through a particular door I try to push it when it's a pull door.
2) I notice this and ensconce in my brain that it's the opposite of what I think it is.
3) After a while, my brain actually starts to remember it the correct way around to start with.
4) But my brain doesn't then drop the 'opposite' rule so now I remember it the correct way around, then invert it, thus recreating step 1.
I don't claim this says anything about rust, but I think it does say something about human brains (or at least mine and apparently yours).
My sympathies.
Of course Rust is not perfect; there is some 'leakages' of low level aspects to high level like async caveats(recursion, pinning, etc.). I'm not sure how these can be avoided. Maybe just trial-and-errors for all..?
If you come to rust from high level language you can just do everything with Rc and cloning.
It's still hard because Rust, in opposition to every pipular language, is a value oriented language. But at least you won't have much contact with alphabet soup.
Albeit I admit there somewhat exists a community sentiment like "if you use Rust, you should maximize its zero cost abstraction feature so lifetime is good and generics good", and my (minor) opinion is that, it's not always true to all users of Rust.
And the clumsy Arc<Mutex<Something<TheOtherThing>>> makes users feel bad about using runtime cost paid types. Maybe we should introduce easy Rust dialect which transpiles into Rc/Clone everywhere but I doubt it's trivial to transpile.
Yeah, this would look worse than any of the "complicated syntax" examples in the blog post.
A language should be designed so that the typical case is the easiest to read and write. Syntax for the most common abstractions. Rust forces you to be explicit if you want to do an Arc<Mutex<Box>>>, but lets you inherit lifetimes almost seamlessly. That means it's not idiomatic to do the first, and it is to do the second.
Languages with a lot of historical baggage don't follow this pattern: if you want to write idiomatic modern C++ it's going to be uglier and more verbose than you see in K&R. But in Rust's case it's clear what the designers encourage you to do.
Cons are:
- C# has OOP (you don't have to use it heavily)
- No Hindler-Milner type inference in C#, nested generic arguments may need to be specified by hand
- Smaller amount of supported targets by CoreCLR: x86, x86_64, arm, arm64 for ISAs and Linux, Windows, macOS and FreeBSD for OSes. NativeAOT-based support is experimentally available on iOS, and is undergoing further work. As you can imagine, LLVM targets absolutely everything under the sun and above it too. no-std story in Rust is first-class. C# has bflat and zerosharp but they are niche.
- In C#, type unions will only be available in one of the coming versions. F# to the rescue
- Error handling is a combination of exceptions and e.g. int.TryParse patterns, there is no implicit returns with ? like in Rust
- Lack of associated types and the types own their interface implementations unlike traits which you can introduce without control over the source. This results in having to implement wrapper types (even if they are structs) if you want to modify their interface implementations
- You only control shallow immutability - readonly struct will not prohibit modification of the contents of a Dictionary<K, V> that it holds
- async/await is more expensive
- Big popular libraries often have features or implementation incompatible or not guaranteed to work with native compilation via NativeAOT
- Object reference nullability (e.g. 'T?') happens at the level of static analysis i.e. does not participate in type system the same way Option<T> does in Rust
Pros are:
- Has FP features like in Rust: high order functions, pattern matching (match val -> val switch { , also 'is'), records, tuples, deconstruction
- Fast to compile and run, AOT not so fast to compile but tolerable
- If you know how to use Cargo, you know how to use .NET CLI: cargo init -> dotnet new {console, classlib, etc.}, cargo run -> dotnet run, cargo build -> dotnet build/publish, cargo add {package} -> dotnet add package {package}
- Monomorphized structs generics with the same zero-cost abstraction assurance like in Rust, &mut T -> ref T, &T -> ref readonly T, sometimes in T but with caveats
- Box/Arc<T> -> class or record, Arc<Mutex<T>> -> class + lock (instance) { ... }
- Easy to use async/await but without ever having to deal with borrow checker and misuse-resistant auto-scaling threadpool. Task<T>s are hot started. Simply call two task-returning network calls and await each one when you need to. They will run in background in parallel while you do so. While they are more expensive than in Rust, you can still do massive concurrency and spawn 1M of them if you want to
- Built-in Rayon - Parallel.For and PLINQ, there is Channel<T> too, you can e.g. 'await foreach (var msg in chReader) { ... }'
- Iterator expressions -> LINQ, seq.filter(...).map(...).collect() -> seq.Where(...).Select(...).ToArray(), unfortunately come with fixed cost but improve in each version
- Rust slice that wraps arbitrary memory -> Span<T>, e.g. can write the same fast idiomatic text parsing on top of them quite easily
- Stupid fast span routines like .IndexOf, .Count, .Fill, .CopyTo which use up to AVX512
- Compiler can devirtualize what in Rust is Box<dyn Trait>
- Can make small native or relatively small JIT single-file executables that don't require users to install runtime
- Rich and fast FFI in both directions, can statically link into Rust, can statically link Rust components into itself (relatively new and advanced)
- Great tooling, Rider is very good, VSCode + base C# extension about as good as rust-analyzer
- Controversial but powerful runtime reflection and type introspection capability, can be used in a very dynamic way with JIT and compile additional code on the fly
- A bit easier to contribute to, depending on area owner and project (runtime, roslyn, aspnetcore, ...)
- CoreLib has full-blown portable SIMD API that is years ahead of portable-simd initiative in Rust
Because I occasionally use Rust and prefer its formatting choices, I carry this .editorconfig around: https://gist.github.com/neon-sunset/c78174b0ba933d61fb66b54d... to make formatting terser and more similar. Try it out if K&R and `I` prefix on interfaces annoy you.
In other words, be careful what you wish for.
Most people would probably be better served by a language that was a tiny bit slower but had better developer productivity. However, once you deviate from the goal of “as fast as possible”, then you have to choose which parts you want to sacrifice speed for productivity. Like Excel, everybody agrees that Rust is too complicated but nobody can agree on which 10% to remove.
D maybe? D and Rust are the two languages which come to mind when I think about "possible C++ replacements".
There’s also the middle ground of Swift’s memory management which uses compiler-elided refcounting - i.e. the compiler detects when a count goes up then down again and removes those operations.
In the face of threading that's not a safe optimisation; if another thread decrements the refcount inbetween those two removed operations, boom. The compiler will have to track every variable that crosses threads or something.
EDIT: spelling
I don't think most people use Rust because it's fast - fast is nice but Rust is being thrown at a bunch of use cases (e.g. backend services and APIs) for which it replaces "slower" garbage collected languages (the language being faster doesn't always make the overall product/service faster but that's a separate question).
What Rust gives you is a viable potential alternative to C and C++ in places where you absolutely can't have a GC language, and that's a huge deal, the problems and confusion start when people try to use Rust for everything.
> everybody agrees that Rust is too complicated
I don't think this is true either - a large part of the Rust community seem to think that it's as complicated as it needs to be. As a beginner/outsider, I found it kind of cumbersome to get started with, but that's certainly not everyone's opinion.
> Most people would probably be better served by a language that was a tiny bit slower but had better developer productivity.
True, and such languages already exist and are widely used, Rust doesn't need to fit that use case.
Personally I feel it's not complicated enough. Where is my function overloading, variadic templates and usable compile time reflection? (Sure you can sometimes use macros but ew macros)
Indeed. Rust is really crying out for a real CTFE implementation + richer macros to replace the mess that is procmacros (I really don't want to have to run an arbitrary external binary with full system access just to manipulate the AST...)
With any language there’s an active part of the community and then there’s the “dark matter” of people who use the language but are not actively involved in shaping its direction, forums or subreddits, etc.
Of course the people who are actively involved are likely to be of the opinion that all the complexity is necessary, but I doubt that applies to the broader Rust userbase.