BTW I think some anti-Rust people are more annoying than the worst Rust evangelists - seen some of them calling people not using Rust as "murderers", because "memory leakage can kill at the right time" - but that's due to them being evangelists to right-wing politics.

all 32 comments

sorted by: hot top controversial new old
[–] 87 points 2 months ago (9 children)

What? You list metaprogramming as a plus? And functional programming as a minus?

You should rewrite this meme in rust.

  • source
  • hideshow 9 child comments
  • [+] [S] -14 points 2 months ago (8 children)

    Did I change a nerve's internal state instead of returning nextnervestate?

  • source
  • parent
  • hideshow 8 child comments
  • [–] 25 points 2 months ago (7 children)

    Rust is all about changing internal state. It even has structures (Cell and others) that specifically allow you to change internal state in otherwise immutable data structures. You should probably learn rust properly before making such claims about it.

    Every foreign language is unreadable until it is not foreign. Acquiring programmer socks helps, too.

  • source
  • parent
  • hideshow 7 child comments
  • [–] 8 points 2 months ago (5 children)

    Changeable state in an immutable data type? Is... Is that a good idea?

    I don't know Rust, but I can tell that the criticism in these image is mostly bullshit. But changing immutable state sounds like a bad idea to me. Is there something I'm missing?

  • source
  • parent
  • hideshow 5 child comments
  • [–] 1 point 2 months ago (1 child)

    "We spent two decades making sure the compiler will never produce memory-unsafe code. It requires a lot of nigh-illegible boilerplate code to even compile and adds massive cognitive load, but the effort will be worth it.

    Anyway, here's a type whose only purpose is to disable all of that shit."

  • source
  • parent
  • hideshow 1 child comment
  • [–] 3 points 2 months ago

    Cell doesn't disable memory safety, though? It comes with additional restrictions such as being unable to share between threads (statically enforced) obviously, it's not an unsafe feature. You also can't read from it unless your type can be bitwise copied, etc.

    Interior mutability is mostly for making immutable interfaces that for some reason or another benefit from storing a bit of mutable state, such as for lazy evaluation. It's also used for cross thread communication in some cases since you have to use shared (immutable) references to share things between threads.

    It requires a lot of nigh-illegible boilerplate code to even compile

    this is the entire source code for an app that performs a rather complex function, note the absence of boilerplatefrom https://codeberg.org/Mycellf/wordjoin

    use std::{
        fs::File,
        io::{self, BufRead, BufReader, Read, Write},
        path::PathBuf,
    };
    
    use clap::{Parser, ValueEnum};
    use icu_segmenter::{
        LineSegmenter, LineSegmenterBorrowed,
        options::{LineBreakOptions, LineBreakStrictness, LineBreakWordOption},
    };
    
    /// Insert utf-8 word joiners (U+2060) to prevent text from wrapping outside of whitespace characters.
    #[derive(Parser)]
    struct Args {
        /// Format and concatenate each file in stead of stdin
        file_paths: Vec<PathBuf>,
    
        /// Read stdin by line instead of all at once
        /// (sometimes worse for interactive use; files are always read by line)
        #[clap(short = 'l', long)]
        by_line: bool,
    
        /// See https://drafts.csswg.org/css-text-3/#line-break-property
        #[clap(short, long, default_value = "strict")]
        strictness: LineBreakStrictnessValues,
        /// See https://drafts.csswg.org/css-text-3/#word-break-property
        #[clap(short, long, default_value = "normal")]
        word_option: LineBreakWordOptionValues,
    
        /// Print this app's GNU GPL-3.0 license
        #[arg(short = 'L', long)]
        license: bool,
    
        /// Print this app's source code
        #[arg(short = 'S', long)]
        source: bool,
    }
    
    const WORD_JOINER: char = '\u{2060}';
    
    fn main() -> io::Result<()> {
        let args = Args::parse();
    
        if args.source || args.license {
            if args.source {
                println!(
                    "Cargo.toml:\n{config}\n\nsrc/main.rs:\n{source}\n\nREADME.md:\n{readme}",
                    config = include_str!("../Cargo.toml"),
                    source = include_str!("main.rs"),
                    readme = include_str!("../README.md"),
                );
            }
    
            if args.license {
                println!("LICENSE:\n{}", include_str!("../LICENSE"));
            }
    
            return Ok(());
        }
    
        let mut options = LineBreakOptions::default();
        options.strictness = Some(args.strictness.into());
        options.word_option = Some(args.word_option.into());
    
        let segmenter = LineSegmenter::new_auto(options);
    
        let mut stdout = io::stdout().lock();
    
        if args.file_paths.is_empty() {
            let stdin = io::stdin();
    
            if args.by_line {
                join_text_by_lines(stdout, stdin, segmenter)?;
            } else {
                join_text_by_all(stdout, stdin, segmenter)?;
            }
        } else {
            for path in args.file_paths {
                let file = File::open(path)?;
    
                join_text_by_lines(&mut stdout, file, segmenter)?;
            }
        }
    
        Ok(())
    }
    
    fn join_text_by_lines(
        mut writer: impl Write,
        text: impl Read,
        segmenter: LineSegmenterBorrowed,
    ) -> io::Result<()> {
        for line in BufReader::new(text).lines() {
            let line = line?;
    
            join_text(&mut writer, &line, segmenter)?;
            writeln!(&mut writer)?;
        }
    
        Ok(())
    }
    
    fn join_text_by_all(
        writer: impl Write,
        mut text: impl Read,
        segmenter: LineSegmenterBorrowed,
    ) -> io::Result<()> {
        let mut input = String::new();
        text.read_to_string(&mut input)?;
    
        join_text(writer, &input, segmenter)?;
    
        Ok(())
    }
    
    fn join_text(
        mut writer: impl Write,
        text: &str,
        segmenter: LineSegmenterBorrowed,
    ) -> io::Result<()> {
        let mut segments = segmenter.segment_str(text).peekable();
    
        while let (Some(start), Some(&end)) = (segments.next(), segments.peek()) {
            let segment = &text[start..end];
    
            write!(writer, "{segment}")?;
    
            if end < text.len()
                && segment
                    .chars()
                    .next_back()
                    .is_some_and(|end| !end.is_whitespace())
            {
                write!(writer, "{WORD_JOINER}")?;
            }
        }
    
        Ok(())
    }
    
    #[derive(Clone, ValueEnum)]
    enum LineBreakStrictnessValues {
        Loose,
        Normal,
        Strict,
        Anywhere,
    }
    
    impl From<LineBreakStrictnessValues> for LineBreakStrictness {
        fn from(value: LineBreakStrictnessValues) -> Self {
            match value {
                LineBreakStrictnessValues::Loose => LineBreakStrictness::Loose,
                LineBreakStrictnessValues::Normal => LineBreakStrictness::Normal,
                LineBreakStrictnessValues::Strict => LineBreakStrictness::Strict,
                LineBreakStrictnessValues::Anywhere => LineBreakStrictness::Anywhere,
            }
        }
    }
    
    #[derive(Clone, ValueEnum)]
    enum LineBreakWordOptionValues {
        Normal,
        BreakAll,
        KeepAll,
    }
    
    impl From<LineBreakWordOptionValues> for LineBreakWordOption {
        fn from(value: LineBreakWordOptionValues) -> Self {
            match value {
                LineBreakWordOptionValues::Normal => LineBreakWordOption::Normal,
                LineBreakWordOptionValues::BreakAll => LineBreakWordOption::BreakAll,
                LineBreakWordOptionValues::KeepAll => LineBreakWordOption::KeepAll,
            }
        }
    }
    

  • source
  • parent
  • [–] [S] -2 points 2 months ago* (last edited 2 months ago) (1 child)

    I have a guess that it was added as a workaround the moment Rust was marketed as a general C++ replacement, and as people started to realize overusing the FP paradigm also has its downsides, it's not unique to OOP.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 26 points 2 months ago

    thinks functional programming = bad

  • source
  • [–] 24 points 2 months ago (15 children)

    Tbh the borrow checker isn't a problem for 75% of cases. If you actually need the performance/memory optimization then yes you will have to deal with it... Otherwise just .clone()

    And if you find the borrow checker annoying in async rust, that's mostly a tokio issue. Look into smol-rs as it offers alternatives

    If you want real cons...

    • Compile times
    • easy build time arbitrary code execution
    • trait bounds spaghetti
  • source
  • hideshow 15 child comments
  • [–] 7 points 2 months ago (7 children)
    • viral async
    • viral lifetime annotations

    🫣

  • source
  • parent
  • hideshow 7 child comments
  • [–] 2 points 2 months ago (6 children)
  • [–] 7 points 2 months ago (3 children)

    When people say "async is viral" in Rust, they mean that once you make one function async, that change tends to ripple through the rest of your code. Any function that calls it usually has to become async as well so it can await the result. In turn, the callers of those functions often need to become async too.

    This propagation can continue all the way up the call stack until you reach your application's entry point. The main exception is when you introduce an explicit synchronous-to-asynchronous boundary, such as by using block_on, which drives the future to completion without requiring the caller itself to be async.

  • source
  • parent
  • hideshow 3 child comments
  • [–] 5 points 2 months ago (2 children)

    Yeah but it's not really a problem with rust but how the language pattern is made. It's the same in JavaScript/typescript, and Python IIRC

  • source
  • parent
  • hideshow 2 child comments
  • [–] 2 points 2 months ago (1 child)

    I've done quite a bit of async programming and I can't quite figure out what people are complaining about here. Best I can tell, they just don't understand what async functions actually are.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 1 point 2 months ago

    Most of the time, async tutorial makes you learn tokio, not async. If your program can run with only tokio::main, then you learned async. If not, you learnt tokio (except if you are spawning a future that should never stop)

    For example, my pet project only uses tokio::main to do async stuff. The only instances of tokio::spawn is make sure some SQLite transactions get polled to completion. I do need to replace them with a proper mechanism now that sqlx supports smol-rs

  • source
  • parent
  • [–] 2 points 2 months ago (5 children)

    And if you find the borrow checker annoying in async rust, that’s mostly a tokio issue. Look into smol-rs as it offers alternatives

    This is great until you want to use a library which is tokio exclusive, which is most of them.

  • source
  • parent
  • hideshow 5 child comments
  • [–] 3 points 2 months ago (4 children)
  • [–] 1 point 2 months ago (3 children)

    Huh, that pretty cool actually. I need to play around and see if this works with gtk-rs, channels get fairly annoying if you need to use them a lot.

  • source
  • parent
  • hideshow 3 child comments
  • [–] 1 point 2 months ago (2 children)

    Keep in mind that it doesn't remove tokio from the stack tho. Don't use this to try to improve compilation time

  • source
  • parent
  • hideshow 2 child comments
  • [–] 1 point 2 months ago (1 child)

    I know, I read the description. It just looks like a nicer syntax around setting up a tokio runtime and sending code between runtimes. It'd still be nice to have a non-tokio options so stuff could be single threaded.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 1 point 2 months ago

    A lot of the time it's not about options. It's about not messing up the async pattern.

    If you have something that either:

    • requires a lot of CPU time
    • requires to run permanently, independently to the caller's future polling. Then you can spawn it on a global tokio executor.

    If not, just use future polling tricks like the futures::join!() macro or a stream with .buffered(). It won't be slower. The bottle neck is IO. Not the program.

    Personally I even try to replace the heavy reqwest library with ureq + blocking, and it works perfectly and compiles faster (you can see that in the api_bindium crate)

  • source
  • parent
  • [–] 12 points 2 months ago

    Compile time borrow checker is equal to runtime GC, right...

  • source
  • [–] 9 points 2 months ago

    "Don't look a gift horse in the mouth"

    No, you probably should look into that horse's mouth to make sure there are no Greeks hiding in it.

    "Beware Greeks bearing gifts"

    Again, no. You should be wary of gifts bearing Greeks.

  • source
  • [–] 7 points 2 months ago

    being annoying online

    hmm

  • source
  • [–] 5 points 2 months ago

    Imagine thinking the borrow checker is just "worse GC." Please at least learn about a language before you try to spread crappy memes about it.

  • source
  • [+] 1 point 2 months ago* (last edited 4 weeks ago)
    [removed by mod]
  • source