• 0 Posts
  • 233 Comments
Joined 3 years ago
cake
Cake day: June 21st, 2023

help-circle

  • If you’re writing a script that’s more than 269 lines long, you shouldn’t be using Bash.

    Jokes aside, the point isn’t the lines of code. It’s complexity. Higher level languages can reduce complexity with tasks by having better tools for more complex logic. What could be one line of code in Python can be dozens in Bash (or a long, convoluted pipeline consisting of awk and sed, which I usually just glaze over at that point). Using other languages means better access to dev tools, like tools for testing, linting, and formatting the scripts.

    While I’m not really a fan of hostility, it annoys me a lot when I see these massive Bash scripts at work. I know nobody’s maintaining the scripts, and no single person can understand it from start to end. When it inevitably starts to fail, debugging them is a nightmare, and trying to add to it ends up with constantly looking up the syntax specific commands/programs want. Using a higher level language at least makes the scripts more maintainable later on.



  • Moreover, you cannot say compilers are deterministic. There are situations where they are not (at least for the user).

    https://krystalgamer.github.io/high-level-game-patches/

    I’m not following. Which part of this is nondeterministic?

    The language being complicated to write and the compiler being confusing to use isn’t an indicator of determinism. If GCC were truly nondeterministic, that’d be a pretty major bug.

    Also, note that I mentioned that the output behavior is deterministic. I’m not referring to reproducible builds, just that it always produces code that does what the source specifies (in this case according to a spec).


  • The library is two text files (code) that are processed by an LLM (interpreter) to generate code of another type. This is not that new in terms of workflow.

    I think what makes this the worst is the fact that the author admits that you can’t be sure the library will work until you generate the code and test it. Even then you cannot guarantee the security of the generated code and as you do not understand the code you also cannot give support or patch it.

    I’ve tried explaining how LLMs are not equatable to compilers/interpreters in the past, and it’s usually to people who aren’t in software roles. What it usually comes down to when I try to explain it is determinism. A compiler or interpreter deterministically produces code with some kind of behavior (defined by the source code). They often are developed to a spec, and the output doing the wrong thing is a bug. LLMs producing the wrong output is a feature. It’s not something you try to fix, and something you often can’t fix.

    This, of course, ignores a lot of “lower level” optimizations someone can make about specific algorithms or data structures. I use “lower level” in quotes, of course, because those are some of the most important decisions you can make while writing code, but turning off your brain and letting a LLM do it for you “abstracts” those decisions away to a random number generator.




  • Funding or not, Miller expects sudo-rs to become the next generation of the tool in coming years.

    “Ubuntu is already shipping sudo-rs as the default sudo command in their latest versions,” Miller told us. “I’ve been in contact with the people working on sudo-rs since the project started and I trust them to do right by the sudo user base.”

    Projects don’t last forever, and when they inevitably end, it’s an opportunity to switch to something newer and hopefully better. Sudo coming to an end, if it does, will just force people onto alternatives.

    Being open source, sudo will always exist, whether someone else wants to maintain it, fork it, use it as-is, or just reference it. It’s because it’s open source that it can serve a purpose even beyond its EOL.

    Anyway, sudo’s not dead yet, so there’s still plenty of time for people to look at what’s out there. Some distros have already moved to, or are considering moving to, alternatives like sudo-rs, so I’d expect that to continue.






  • Is a database handle you can write to not … basically mutable state, the arch-nemesis of functional languages?

    Access to an external database is a kind of effect. In functional terms, you’d use a monad to represent this, though in Koka you’d define an effect for it. In either case, it becomes part of the function signature.

    A “pure” function could instead work around it by returning (without executing) a query, accepting a state and returning a new, modified state, or otherwise returning some kind of commands to the caller without directly querying the database.


  • This puts far too much control on the LLMs. A LLM can provide suggestions for a PR, but those suggestions are not a sufficient replacement for a real review.

    If the rate of PRs is too high to review, the solution isn’t to sacrifice the reviews. It’s to ensure that the PRs are of sufficiently high quality that the reviews are quick. Small PRs are faster to review, and readable code is easier to review. Tests can validate correctness to the reviewer. Make the review process as easy as possible for a proper code review.

    The hybrid approach seems to me like it’d be the most successful here. Generate your PR suggestions, and let the PR owner resolve them how they like. Then, do a proper review on the PR. Where I disagree with the author here is the reviewer shouldn’t review the suggestions and resolutions, but the final diff instead.



  • This does seem like an issue with the library you’re using. Your second solution, using RawValue, is likely what I would have gone with, bundled with a self-referential type (wrapping the Pin in another nicer-looking type though). This is assuming I want to pass around a 'static type with the partially-deserialized data. In fact, I’ve done something like this in the past to pass around raw log data next to that same data in a parsed format (where the parsed data borrows from the raw logs).

    Alternatively, I’d have deferred the lifetime problem to someone else (library user for example) since the source data is probably provided at a higher level. This is how the libraries you’re using do it from what I can tell. They make the LT the user’s problem since the user will know what they want to do with the data.


  • If I had chosen to write this code in Go I never would have had to think about any of this and I expect it would have been fine.

    Well the article is about zero-copy deserialization in Rust. If you just slap #[derive(Deserialize)] on a bunch of 'static types and let it copy the strings, then you don’t have this issue in Rust either.

    Also, you’d be using Go, not Rust. That’s fine, but not really relevant when you want to do JSON deserialization in Rust, is it?

    What a wild conclusion to come to.


  • Breaking down what async fn in trait does, it converts it to a fn method() -> impl Future<Output=T>, which breaks further down into fn method() -> Self::__MethodRet where the generated associated type implements Future.

    This doesn’t work for dyn Trait because of the associated type (and the fact method() needs a dyn-safe self parameter).

    Instead, your methods need to return dyn Future in some capacity since that return type doesn’t rely on associated types. That’s where async_trait comes in. Box<dyn Future<...>> is a dyn-safe boxed future, then it’s pinned because async usually generates a self-referential type, and you need to pin it anyway to .poll() the resulting future.

    Edit: also, being pedantic, associated types are fine in dyn traits, but you need to specify the type for it (like dyn Blah<Foo=i32>. Even if you could name the return type from an async fn, it’d be different for every impl block, so that’s not realistic here.


  • While I agree with your post, I do want to call out that Rust’s standard library does use a lot of unstable features and calls compiler intrinsics. Anyone can use the unstable features I believe with just #![feature(...)], but not the intrinsics (not that there’s much reason to call the intrinsics directly anyway).


  • You can design a language where you don’t need to generate code to accomplish this.

    Depending on what you mean by “generate code”, the only language at the level of C or C++ that I can think of that does this is Zig. Zig is weird though because you’re still doing what is functionally compile-time reflection, so in a way you’re still generating code, just in a different way.

    If you’re comparing to Python, JS, or even C#, those all come with runtimes that can compile/interpret new code at runtime. None of those languages are comparable here. Rust, C, C++, Zig, etc compile into assembly, and type information, impl information, etc are all lost after compilation (ignoring symbol names or anything tracked as debug info).

    If you’re specifically referring to Debug, Display, PartialEq, etc then the compiler doesn’t do that for you because Rust doesn’t assume that those traits are valid for everything.

    Unlike Java where new Integer(1) != new Integer(1) or JS where "" == 0, Rust requires you to specify when equality comparisons can be made, and requires you to write out the implementation (or use the derive for a simple, common implementation).

    Unlike C# where record class Secret(String Value); will print out the secret into your logs when it inevitably gets logged, Rust requires you to specify when a type can be formatted into a string, and how it should be formatted.

    Just because a language does things one way doesn’t mean every language ever should do things that same way. If you want it to work like another language you like to use, use the language you like to use instead. Rust language designers made explicit decisions to not be the same as other languages because they wanted to solve problems they had with those languages. Those other languages are still usable though, and many solved the same problems in other ways (C#'s nullable reference types, Python’s type hints, TypeScript, C++'s concepts, etc).