• 1 Post
  • 43 Comments
Joined 2 years ago
cake
Cake day: July 1st, 2023

help-circle


  • I’m talking about compile time.

    Start with all of the known safe cases (basic types should be fine), then move on to more dubious options (anything that supports iteration). Then allow iterable types but don’t allow iterating over a mutable reference. And so on. If it’s a priority to loosen up the rules without sacrificing safety, surely some solutions could be found to improve ergonomics.

    If you want guaranteed safety then the borrowing rules are the most flexible as far as we know.

    Just to give a couple of examples of how your idea might be flawed, what do you consider “basic types”? Are enums basic types? Then you’ve got an issue, because you might get a reference to the contents of an enum and then replace the enum with another variant, and suddently you’ve got a dangling reference. Would you prefer to prevent creating references to the contents of an enum? Then you’re more restricting than the borrowing rules.

    Allowing iterable types but not iterating over mutable references is not enough for safety unfortunately. The basic example is getting a reference to an element of a Vec and then calling push on the Vec. This seems very innocent, but pushing on a Vec might reallocate its backing buffer, making the previous reference dangling. Again, would you prevent taking references to elements of a Vec? Then again you become much more restricting than the borrowing rules.

    I really liked the idea of an optional, built-in GC w/ pre-1.0 Rust where specific references could be GC’d

    That was just syntax sugar for Rc/Arc, and you can still use them in today’s Rust, albeit with slightly worse ergonomics (no autoclone for example).


  • Sure, but that doesn’t mean it can’t be better.

    It’s surely interesting though that people continuously complain about them and then praise a language whose equivalent feature is much more restrictive!

    Surely the compiler could delay optimizations until the entire project is built, no? Then it knows what implementations exist, and the developer could then decide how to deal with that

    It’s not really about optimizations but rather:

    • when checking some impls for overlap, the compiler assumes that impls that the orphan rules block will never exist. You thus need to either disallow them (which would make the compiler more restrictive for non-application crates!) or a way to track them (which is easier said than done since coherence and trait checking is very complex)

    • when generating code where specialization and/or vtables are involved. This could be delayed until the last crate is compiled, but at the expense of longer compile times and worse incremental compilation performance.

    Sure, and ideally those cases would be accounted for,

    AFAIK there’s nothing yet that can account for them without allocating everything on the heap.

    or at the very least the dev could annotate each use to turn the borrow checker off for each instance, and that could print something at build time and a linter could flag over it. Unsafe blocks aren’t feasible for everything here.

    You want some annotations to break out of the safe subset of the language but aren’t unsafe blocks basically that? Or perhaps you want something more ergonomic, at the expense of safety?


  • dreaded orphan rule

    Yeah, that always struck me as stupid.

    It is necessary to guarantee consistency in the trait system, otherwise it could lead to memory unsafety. Even relaxing it in cases where overlapping implementations could always be catched is still problematic because the compiler sometimes performs negative reasoning about traits that it know cannot be implemented downstream due to the the orphan rule.

    And if you think about it the orphan rule is not worse than what other languages allow. For example C# only allow implementing an interface when defining a type, while the orphan rule also allows you to implement a trait when defining the trait itself. Not to mention being able to implement a trait only when a generic parameter implements another trait.

    Yeah, the borrow checker is a bit too strict IMO. Ideally, the borrow checker would only trigger on things that could be run in parallel, such as with threads or async.

    You can still trivially violate memory safety without multithreading or concurrency. The article touches on this a bit (they mention e.g. iterator invalidation) but they fail to address all issues.

    https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/



  • Giooschi@lemmy.worldtoRust@programming.devTypst is hiring
    link
    fedilink
    English
    arrow-up
    0
    ·
    3 months ago

    While they don’t write it explicitly I think they’re looking for a good junior developer, given that:

    • they are not asking for Rust work experience, instead for good Rust knowledge and experience with open source development, both of which you can obtain on your own if you’re a competent student

      • but also, is there even anyone that has experience in Rust and compiler/interpreter/typesetting development and is looking for a job? If they did require that almost nobody would qualify and the cycle of “I don’t have experience for applying to this job to get experience” would continue
    • 57k€ is not a bad salary for a junior developer in Europe

    • the two founders have graduated recently (~3 years ago) and have been working on Typst since then (their master thesis was on creating Typst itself), so it’s likely they are looking for someone like them.



  • Giooschi@lemmy.worldtoRust Programming@lemmy.mlWhich is faster?
    link
    fedilink
    English
    arrow-up
    0
    ·
    edit-2
    4 months ago

    let statements only reserves space on the stack

    It is not guaranteed to do that. It could also use a register or be optimized out completly (like in the example you posted in the other comment).

    The stack pointer is also not changed for each local variable, but instead for each function call, so it wouldn’t make a difference anyway.



  • I hate all the cruft in my home directory, but I also hate when stuff suddently stop working after an update, or when all the documentation online talks about something that doesn’t work on my system or is not there anymore. Developers are the ones that will have to deal with people with these issues, so I can see why they are reluctant to implement the naive solutions that some ask for.




  • Well, but then you’re basically just pushing the mutability onto the container

    That’s the point, when programming with immutable structures you always pass the mutability onto the enclosing structure.

    It’s a good strategy at times though. Like say you’re working in a language where strings are immutable and you want a string you can change. You can wrap it in a list along the lines s=['foo'] and pass references to the list around instead. Then if you go s[0]='bar' at some point, all the references will now see ['bar'] instead.

    A list is an antipattern here IMO. Just wrap it in some dedicated object (see e.g. Java’s StringBuilder).






  • What? You can easily escape from it if there are better alternatives you can use.

    So there is no general escape hatch.

    Pointing at one language and saying it is not easy to code like it is another language is a pointless argument.

    I’m not arguing that it is easier to code in C# than in Rust, just that this particular escape hatch is possible in C# and not in Rust. It’s just an observation.

    They all differ for good reasons and as long as you can solve similar problems in both, even if in different ways then what does it matter that you cannot do it in the same way?

    It does not really matter, but does it have to?


  • This is a kind of stupid example, but imagine you have to implement some external trait (e.g. in order to work with some dependency) with the following shape:

    trait Foo {
        fn foo(&self, i: usize) -> &Bar;
    }
    

    Which is not too unreasonable, for example it’s very similar to the stdlib’s Index. In order to implement this trait you must return a reference, you can’t return e.g. a Cow or an Arc. The fact that it takes a parameter means there might not even be one single value it has to return, so you can’t even cache that inside of self with e.g. LazyLock.

    Of course I’m not saying I would try to reach for an escape hatch if I had to do something like this. I would first try to see if this is an intrinsic problem in my code, or if maybe I can change the crate I’m working with to be more permissible. That is, I would try to look for proper solutions first, though Cow might not always work for that.