I stopped with rust

Yes definitely do, while it won't result in Rust radically changing it's syntax or anything like that (backwards compatibility guarantee); it is very useful evidence when people debate about further changes to the language.

You could help avoid even more complex syntax being added, for example :slight_smile:

3 Likes

In fairness I think 8 months is plenty of time to give something a decent go and conclude it’s just not for you

14 Likes

Rust is verbose and the type system is complex. If I could get both (1) maximum performance (at the C/C++ level, meaning no GC) and (2) safety/reliability with a simpler language, I probably would. But currently Rust is the only language that provides these two benefits. This tells me that doing so is an extremely difficult problem; if it were not, there would already be a simpler language that does it. And fortunately with Rust we have great libraries, dev tools, doc and community, which compensate to a large degree for its difficulties.

So if you don't need those two benefits, choosing a simpler language makes sense. However, if you intend to write software that is actually used, I do not recommend choosing an unsafe language (C, C++, Zig, etc). These are not simple languages anyway (even with Zig, comptime is quite complex), and choosing an unsafe language will perpetuate the security and reliability problems we have today. I would instead recommend choosing a safe language that uses GC, since many GC-based languages are much simpler. And their performance is adequate in many cases.

5 Likes

Yes, especially if the user has many years of experience with other programming languages.

2 Likes

try Kotlin.

I don't mean to attack this, sorry, but do you mind expanding on these somewhat?

Certainly things like trait bounds can get pretty awful, and separate impl blocks are an initial shock, but I've not found Rust to have any notable verbosity in it's space (it's obviously nowhere near as succinct as dynamic languages) - but this is a somewhat subjective concern, so it's hardly surprising someone might feel differently!

That said, I found many tricks that significantly reduced what I thought was required verbosity: in particular trait bounds are generally not required on the type, and bounds don't actually need to be a bound on the parameter implementing a trait, which allows much clearer boundaries like Builder::Output : Into<Result>

The type system certainly has some nasty corners, especially things like fat pointers and "object safety", but boy do I miss it overall when I'm using other languages. Proper discriminated enums and traits are so powerful for how quick they are to pick up the basics! Perhaps I'm missing something?

3 Likes

I understand much of you concerns. I have been using Rust for five years, I am a very experienced programmer and computer scientist, and I still feel, like you, that it is sometimes a puzzle to decipher. I have been using (and for some of them teaching) almost every possible programming languages, starting with Cobol, Fortran and assembly languages, but including now C, C++, ADA, Modula, Ocaml, Zig, python, Haskell, etc.
If you are unhappy with Rust, try ZIG (someone already mentioned it). It is not yet very mature, but it is blazingly fast and it does protect you from some problems C has regarding memory access and memory management. My favorite language remains Ocaml, that you should also try. Rust comes partly from Ocaml, with the additional goal to live without a GC, but Ocaml is much easier to master, and much more "orthogonal" regarding the choices of its design. ADA was also a great language, but which never got much traction, mainly because when it started there was a lack of a good integration with Unix.
There is also a problem, according to me, with the Rust programming community. While most people are very helpful (and extremely competent), there is a part of the community (and a larger part than in other communities according to my experience) of condescending people. If you don't understand a point, you're an idiot. If you dont like a part of the language, you're also an idiot, etc.It sometimes feels like a cult you have to enter and believe in. Unfortunately for them, there are some parts of the language which are debatable, as there have been debatable choices such as for example having different "meanings" for the let a=b instruction, which behaves differently regarding the nature of b (Copy or non-Copy). There are good reasons for that, but it is however debatable, especially when you are learning the language.
Anyway, I think it's sad to see someone leave. Rust is a great language, even if it has some shortcomings like any language, and it is worth the effort. However, like any language, it has to be picked according to the task you have at hand. I personnaly use it when I need to write a code "rock solid", that will be easy to maintain. But I won't use it for everything, and it is pretty understandable it might not meet your needs.

4 Likes

I'm 50+ as well and starting with Rust second time, and it's hard and takes time. Passed through one more online course and doing tasks at Exercism now (still at task 3 hehe) when I have time.
When easy tool required to do the work I always have Perl in my pocket! :slight_smile:
I hope you will come back.

3 Likes

Pretty long thread. I will share my opinion. Most of your frustrations originate from what we know already - say the native programming language that we are very comfortable with. But, with time and motivation, it is really possible to overcome all these frustrations.

I did find it difficult when I started, but the strong positives that are part of Rust really helped me get going and today I can say that I am comfortable with the language. It will take a while before I can call myself a skilled Rust programmer, but I can confidently say that with practice, I will become one.

To all those in the same boat of getting frustrated with Rust, my simple advice is - don't start with advanced things. Rust can be very very overwhelming. Get comfortable first and then see how you can update the code based on what you learn new.

2 Likes

Btw I tinkered with your function and refactored it to this which I think it somewhat more readable, less indented

use std::collections::HashMap;
use std::fs::read_to_string;

const PROC_MEMINFO: &str = "/proc/meminfo";

pub fn meminfo() -> std::io::Result<HashMap<String, usize>> {
    let text = read_to_string(PROC_MEMINFO)?;
    Ok(text.lines()
        .filter_map(|line| line.split_once(':'))
        .filter_map(|(key, value)| value
            .trim()
            .split_once(' ')
            .map(|(value, unit)| value
                .trim()
                .parse::<usize>().ok()
                .and_then(|value| match unit.trim() {
                    "kB" => Some(1024 * value),
                    _ => None,
                }))
            .unwrap_or(value.trim().parse::<usize>().ok())
            .map(|value| (key.trim().to_string(), value)))
        .collect())
}
1 Like

FWIW digsigctl/src/sysinfo/meminfo.rs at a17684a88b629d13911266df5f6ac2548f00d0f2 · homeinfogmbh/digsigctl · GitHub

3 Likes

Sorry if I sounded a little blunt. I was mainly trying to empathize with the OP, since I also appreciate simplicity very much. I wasn't trying to compare Rust verbosity to other languages in its space, if by that you mean systems languages without GC. And the type system complexity is much more of a difficulty for me personally than the verbosity.

I do think Rust is more verbose than most languages with GC, for one because there is much less need for generics. And when you do use generics, they seem much simpler because you don't need so many trait bounds to make things work. In addition to simpler generics, when everything is a reference there is just much less going on in general, and therefore less to think about when reading the code of a given function. So I guess it is really the complexity that makes it feel more verbose.

I do not think either of these problems are because of Rust design errors (although I'm sure there are some, I don't even know of any off the top of my head), I think it is because of what it is trying to accomplish: providing a language that can replace C/C++, that does not compromise performance, that is memory safe, data race free, and promotes reliability in a bunch of other ways (sum types, etc, etc). This is the main point I was trying to make.

Verbosity and type system complexity are only two aspects of the language, but from what the OP said I think these were the things causing them to give up. There are other criticisms of Rust that could be made (particularly in the areas of async and traits), but these are well known problems that are being worked on, and we're seeing improvements in these areas all the time.

2 Likes

I don't really have a favorite language either - from a pragmatic point of view it is rust.
Used to be C/C++ and python.

I've explored several other languages in the recent past

  • Erlang - interesting language and efficient if your problem fits the programming model (WhatsApp). But it is quite slow for numerical work - faster than python, but similar situation with having to write extensions as c-modules to get compute heavy code to run fast.
  • Clojure - lisp - also interesting - and always interesting to hear Rich Hickey talk about it. In practice it was much slower than I expected. It is a good alternative to java though - since it runs on the jvm.
  • Mojo - still too experimental, and I think the "python heritage" is too forced to be natural.
  • Fortran - great language (for numerical work) - the language has been modernised and it has most of the features you would expect today. Tooling is weaker than rust - there is a cargo inspired build tool that works ok, but no package manager and it is sometimes hard to find libraries that do common stuff you don't want to implement yourself or bridge via c - such as reading images in different formats etc. Also, a bit too many memberberries on the forums.
  • Julia - good language, but not general purpose (scientific) - and I prefer statically compiled.

Rust has some really nice core features - enums, pattern matching, macros, great tooling - and then features that I feel are a work in progress.

Trait bounds often get unwieldy - recently implemented a struct with two integer arrays - u8 and i32, but I didn't want to limit it to those choices. Possible, but got surprisingly complicated to maintain when they had to interoperate.

Macros are great - obviously a different class than c macros - but I feel the langauge (declarative macros) is experimental and more meant for the language designers than the users of the language.

Lifetimes also need some work - ideally the compiler should be better at figuring them out :slight_smile:

3 Likes

and:

In my opinion, both approaches offer minimal gains; simply breaking down a complex construction into functions or using more compact notation doesn’t significantly improve the situation.

Refactoring should target the root cause of complexity. The most important aspect, I believe, is that code should be readable, comprehensible, and easy to understand.

With that said, I’d like to carefully suggest the following approach:

pub fn meminfo() -> std::io::Result<HashMap<String, usize>> {
    let text = read_to_string(PROC_MEMINFO)?;
    Ok(meminfo_from_text(&text))
}

fn meminfo_from_text(text: &str) -> HashMap<String, usize> {
    text.lines().flat_map(parse_line).collect()
}

fn parse_line(line: &str) -> Option<(String, usize)> {
    let mut token = line.split_whitespace();

    let key = token.next()?.strip_suffix(':')?;
    let value: usize = token.next()?.parse().ok()?;
    let unit = token.next();

    let factor = match unit {
        Some(unit) => unit_to_factor(unit)?,
        None => 1,
    };

    if token.next().is_none() {
        Some((key.to_string(), value * factor))
    } else {
        None
    }
}

fn unit_to_factor(unit: &str) -> Option<usize> {
    match unit {
        "kB" => Some(KIB),
        _other => None,
    }
}
3 Likes

I mean, it's certainly better than C++, but that's a low bar to clear.

The generics syntax (turbofish) is just horrible. The only reason it exists this way is to resemble C++ templates, otherwise generics should just be in brackets. The :: namespacing token is pretty verbose, certainly more than the usual dot. There is no way to do implied trait bounds, or trait bound aliases, or trait bounds shared within a module. Types like Option or Result are quite common, could use a special short syntax (like Int? in Kotlin). Closures require lots of typing and syntax (again, compare with Kotlin or Scala).

There are no optional and defaulted function arguments (I don't miss writing builders).

Repeating the standard derives on types is also verbose, most types could do with a standard bunch of applicable derives, and a syntax for opt-out. Items being private by default mean that you need to write lots of pub on export-heavy APIs.

Orphan rules are just a pain.

format!()? How about we just have format strings, like in Python? Also with proper expression interpolation, not the trimmed "only identifiers" version we have. Also, string literals could be polymorphic over the resulting string type (e.g. create String or Box<str> with the same string literal syntax). Oh, and a first-class boxing operator. No more Box::new().

Macros are quite middling. Better than if we didn't have them, but surely a better system could be designed, if it were a priority. Hell, syn could be part of the standard library, and also it could be prettier.

We could go even further: await and const blocks could be implicit (correct behaviour inferred from usage). Combinators on Option and Result could use a special syntax. Let's do away with braces and use indentation-based syntax, people autoformat everything anyway. Unsafe code could use quite a bit of syntax sugar (e.g. look what C does). Pin... don't even get me started. Closures could use quite a bit of sugar for simple cases (e.g. what if I could do zip(a, b).map(+) and get a vector of sums? yes, collect() omitted on purpose).


Tons of stuff one could change if one were to design Rust for maximum legibility. Some of the stuff above is entirely incidental and could be easily changed, like syntax borrowed from C++. Some is contentious for no good reason, like optional arguments. Some is critical for ecosystem stability, like orphan rules, but could be easily dropped in a language with different priorities. Some is designed for convenience of building large-scale long-living systems (like default privacy), and could be changed in a language targeting the market of novices and small-scale products. Some help with correctness and auditability (like lack of implicit conversions), but also incur a greater cognitive burden. Some limitations exist for purely technical reasons (e.g. the backwards compatibility coupled with the fragility of type inference kill a lot of possible improvements).

Different people will disagree on what exactly makes Rust great and could make different choices, even without touching the core of borrow checker, type inference, Hindley-Milner inspired type system or traits.

5 Likes

@afetisov
The kind of things you name were things that did not motivating me...
Actually the turbofish :: I like (in the code, not in the usings), because of the ultra clarity what you are dealing with.
In general for me it is: there are many very good things in Rust, but a few things I really dislike.

1 Like

Great stuff! Certainly there's lots of sharp edges in Rust, I just don't generally put them firmly in the "verbosity" or "complex type system" buckets.

Something that I'm occasionally prompted to think about is what a "simple Rust" would look like; obviously there's things like Rune (which is basically "if Rust was a dynamic language"), but every time I think about trying to pull away features from Rust to get a systems language with no runtime and with reasonable safety guarantees, the whole thing starts to fall apart.

I mention this because a lot of the sugar you posited are the sort of thing you might expect a modern language the size of Rust to have to be thinking about, and also the sort of thing you could remove from a language to get a simpler system. But Rust seems to like generally (with occasional exceptions like ?) build with very broad, load bearing features like associated types and derives that provide the underlying functionality of a dozen little user facing features. Rust without those would be very barren indeed!

2 Likes

My first month of learning and writing Rust was very intense and positive. I already felt that I'm efficient in Rust, and then ran into a problem of 'already borrowed: BorrowMutError'. This required me to stop for a few days and think more deeply about the borrow checker. An unobvious consequence of the borrowing rules is that Rust does not like cyclic dependencies: this applies to data structures and, more importantly, the function call infrastructure (cycle is not possible, when we are dealing with &mut self, and tree is required instead). This requires additional pedantic complexity of the architecture in some cases, but this is a solvable problem, and it can be considered a small cost to pay for the increased reliability of the system.

The second month of working with Rust became even more comfortable and productive. But then I stumbled upon the second big limitation of the language. I avoided using global state, but in some cases it is necessary in my project. It turned out that "static mut" might be deprecated. Space of other solutions was not obvious and required research, and it was overwhelming a bit. I spent several days, reading docs and forums. I use statics with interior mutability now.

People say that Rust is difficult, but for me it was difficult only in these two aspects. And I understand why the difficulty in them is necessary, considering the priorities of Rust. In general, I found learning Rust comfortable and enjoyable.

1 Like

I think a more useful distinction than "verbosity" or "complex type system" is "who benefits from this complexity?"

Looking over @afetisov's list, I see three important groups (and some things fall into multiple groups) affected by the complexity of Rust:

  1. The compiler and language specification teams (such as T-opsem); there are cases where fully defining an unambiguous and unsurprising meaning is really hard, and therefore pushing it to the programmer makes sense for now, while teams work on getting that definition sorted.
  2. Initial development programmers. These people are trying to get something to work to see if it's worth developing into a full-blown system, or whether it's actually a bad idea for reasons they couldn't predict up-front. As a consequence, they care greatly about the time it takes to write code, but not about the time it takes to read it.
  3. Maintenance programmers. These people are coming to a program they've not worked on before, and reading the code to understand how to make a desirable change (a bug fix, a new feature, whatever's needed here), before changing the code and moving on. As a consequence, they care greatly about it being easy to follow the code when they read it (and to not have to keep too much context in their heads), but not so much about the time taken to write it.

Note that people don't fall rigidly into these three groupings; you can be in all three, and it's unusual for you to only ever do one of these three roles. Also, there's other roles I'm not listing; these are just the three that come to mind when reading the list.

The difficult part is what we do when two roles are diametrically opposed; const blocks are a good example, since for the initial development programmer, the fact that a block has to be const is obvious from context, while the maintenance programmer benefits from seeing that refactoring such that the const block depends on runtime data is going to be an expensive refactor.

4 Likes

That seems to be a rather bold statement. If C++ is the low bar then which language(s) do you consider would be the high bar?

I ask because personally I don't know of any other language besides Rust and perhaps Ada that matches C++ in:

  1. Providing about the best performance, as we can expect from a compiled language.
  2. Producing executables about as small as we can hope for.
  3. Being very portable, from tiny micro-controllers up.
  4. Providing lots of pretty high level language abstractions.
  5. Not suffering from latency problems and bloat caused by requiring a run-time or garbage collector.

Given that we are working up to such a high bar whilst I might agree with some of your long list of annoying or lacking features in Rust on the whole I don't find any of that worse for Rust than the similar list of grievances I could draw up for C++ or Ada. What sells me on Rust when balancing all those complaints one can have about languages is of course Rusts anal type and lifetime checking which no other language has and help greatly with robustness and reliability.

So what high bar are you looking at? Which languages are up there?

4 Likes