There's a good chance I can make use of str::substr_range on a current project of mine. I'll be storing some substrings from str::split, and now I can store the original string and ranges of indices into it instead of making an owned string for every substring.

[-] shape_warrior_t@programming.dev 4 points 2 weeks ago* (last edited 2 weeks ago)

Just finished "version 0" for a very simple programming language yesterday, and I'm looking to work on "version 1" this weekend. Rust is truly fun to program in... although writing all the tests was not a very fun experience. But doing the work for that meant I was guaranteed to have a reasonably good grasp of the semantics by the time I finished.

Since it's Rust, there's no garbage collector -- heck, for this particular language, there's not even any memory allocation after startup. Instead you get a giant global array of "memory" to work with. The only data type is the 32-bit integer (and thus, using UTF-32 for strings is actually the """correct""" choice for this language). I'm using a vaguely Lisp-like syntax that revolves around {[a tree] formed from (brackets and whitespace)}... which was probably slightly easier to parse than more traditional syntax, but Rust's current lack of pattern matching "through" Vec (for example, letting you extract e from {a b [c (d e)] f} in one step) didn't always make it feel that way.

Nest up: adding while loops, exponentiation and bitwise operators, and most interestingly, string and character literals. (The char literals would just evaluate to their character codes, and string literals would only be usable in a specific write-to-memory construct.) And also expanding the standard library. I'd say "version 1" would really be the more properly "complete" version of the language. I also want to do a functional programming variation (ditching the global memory in favour of reference counted cons cells), and also the whole language is kind of just a stripped down version of another much bigger language half-formed in my head, but that's all probably for... not this weekend, at any rate.

56

Found this on Lobsters, thought it was an entertaining read. For more context, you might want to read the previous instalment, CVE-2024-YIKES (also linked early on in the post itself).

27
submitted 6 months ago* (last edited 6 months ago) by shape_warrior_t@programming.dev to c/programmer_humor@programming.dev

Saw this on Lobsters, thought Lemmy might enjoy it.

54

We adopted Rust for its security and are seeing a 1000x reduction in memory safety vulnerability density compared to Android’s C and C++ code. But the biggest surprise was Rust's impact on software delivery. With Rust changes having a 4x lower rollback rate and spending 25% less time in code review, the safer path is now also the faster one.

(Emphasis in original.)

Of course, we should probably take the numbers with a grain of salt here; it's not a controlled scientific experiment, and the estimated vulnerability density for Rust in particular was calculated with a numerator of 1, so it could be way off.

Still, I think it's a good reminder that advocacy for using Rust over C and C++ isn't purely out of some irrational fanboy-ism for the language. The numbers here would have to be extremely off for there not to be a major effect.

Further down in the article:

This near-miss inevitably raises the question: "If Rust can have memory safety vulnerabilities, then what’s the point?"

The point is that the density is drastically lower. So much lower that it represents a major shift in security posture. Based on our near-miss, we can make a conservative estimate. With roughly 5 million lines of Rust in the Android platform and one potential memory safety vulnerability found (and fixed pre-release), our estimated vulnerability density for Rust is 0.2 vuln per 1 million lines (MLOC).

Our historical data for C and C++ shows a density of closer to 1,000 memory safety vulnerabilities per MLOC. Our Rust code is currently tracking at a density orders of magnitude lower: a more than 1000x reduction.

[-] shape_warrior_t@programming.dev 7 points 11 months ago

The fact that x += y modifies lists in place might be surprising if you're expecting it to be exactly equivalent to x = x + y.

There was a recent langdev Stack Exchange question about this very topic. It's a bit trickier to design than it might seem at first.

Suppose we require a keyword -- say var -- before all binding patterns. This results in having to write things like
for (&(var x1, var y1, var z1), &(var x2, var y2, var z2)) in points.iter().tuple_windows() {},
which is quite a bit more verbose than the current
for (&(x1, y1, z1), &(x2, y2, z2)) in points.iter().tuple_windows() {}.
Not to mention you'll have to write let var x = 0; just to declare a variable, unless you redesign the language to allow you to just write var x = 0 (and if you do that, you'll also have to somehow support a coherent way to express if let Some(x) = arr.pop() {} and let Some(x) = arr.pop() else {todo!()}).

Suppose we require a keyword -- say const -- before all value-matching patterns that look like variables. Then, what's currently

match (left.next(), right.next()) {
    (Some(l), Some(r)) => {}
    (Some(l), None) => {}
    (None, Some(r)) => {}
    (None, None) => {}
}

turns into either the inconsistently ugly

match (left.next(), right.next()) {
    (Some(l), Some(r)) => {}
    (Some(l), const None) => {}
    (const None, Some(r)) => {}
    (const None, const None) => {}
}

or the even more verbose

match (left.next(), right.next()) {
    (const Some(l), const Some(r)) => {}
    (const Some(l), const None) => {}
    (const None, const Some(r)) => {}
    (const None, const None) => {}
}

and you always run the risk of forgetting a const and accidentally binding a new match-all variable named None -- the main footgun that syntactically distinguishing binding and value-matching patterns was meant to avoid in the first place.

Suppose we require a sigil such as $ before one type of pattern. Probably the best solution in my opinion, but that's one symbol that can no longer be used for other things in a pattern context. Also, if you're already using sigils before variable names for other purposes (I've been sketching out a language where a pointer variable $x can be auto-dereferenced by writing x), doubling up is really unpleasant.

...So I can understand why Rust chose to give the same, most concise possible syntax for both binding and value-matching patterns. At least compiler warnings (unused, non-snake-case variables) are there to provide some protection from accidentally turning one into the other.

23
Long Names Are Long (journal.stuffwithstuff.com)

A nice little reminder that clarity is not the same as verbosity. Also has some concrete tips for removing unnecessary verbosity in names, complete with examples. Though in some contexts, I might prefer a name like employeeToRole for a Map<Employee, Role> over the article's employeeRoles.

7

I think this is a decently interesting consideration in programming language design. When you have things flowing from left to right, not only do you get better autocomplete as per the article, but it's probably also easier to read, and very likely easier to edit (your cursor can simply go in its natural direction instead of having to jump around).

Though this comment on Lobsters points out that left-to-right code under one way of thinking is not necessarily left-to-right code if you approach it with a different way of thinking.

[-] shape_warrior_t@programming.dev 9 points 1 year ago* (last edited 1 year ago)

I was thinking of the three legal states as:

  • not logged in (null or {isAdmin: false, isLoggedIn: false})
  • logged in as non-admin (false or {isAdmin: false, isLoggedIn: true})
  • logged in as admin (true or {isAdmin: true, isLoggedIn: true})

which leaves {isAdmin: true, isLoggedIn: false} as an invalid, nonsensical state. (How would you know the user's an admin if they're not logged in?) Of course, in a different context, all four states could potentially be distinctly meaningful.

a === b returns true if a and b have the same type and are considered equal, and false otherwise. If a is null and b is a boolean, it will simply return false.

I would certainly rather see this than {isAdmin: bool; isLoggedIn: bool}. With boolean | null, at least illegal states are unrepresentable... even if the legal states are represented in an... interesting way.

[-] shape_warrior_t@programming.dev 7 points 1 year ago* (last edited 1 year ago)

Nice! Some feedback (on your Python, I don't speak Greek):

  • In Python, the idiomatic way to name variables and functions is snake_case -- for example, greekTranslation should be greek_translation. (EDIT after seeing your most recent reply: good luck with that when it comes to Python :) )
  • You're currently recomputing reverseTranslation every time the user requests an English-to-Greek translation. Unless you're planning to modify greekTranslation while the program is running, it would likely be more efficient to make reverseTranslation a global variable, computed just once at the start of the program.
  • The control flow in the main program could be a bit more clear:
    • The condition in while optionSelection == <'practice'/'translation'> makes it look like optionSelection could change to something else inside the loop, yet you never do so. You could just write while True instead.
    • Instead of putting the "Please select either practice, translate, or exit" check up front, it would likely be more maintainable to add it as an else branch to the main if-elif-elif chain. That way, if you added in a new option, you would only have one place in the code to modify instead of two. (Also, instead of x != a and x != b and x != c, you could write x not in [a, b, c]).
    • Speaking of the if-elif-elif chain, you could replace it with a match statement, which would remove the repetitions of optionSelection ==.

Here's how I would write the control flow for the last section:

    optionSelection = input().strip().lower()
    match optionSelection:
        case 'practice':
            while True:
                practiceGreek()
                print('')
                print('Would you like another? [yes] [no]')
                print('')
                selection = input().strip().lower()
                print('')
                if selection != 'yes':
                    break
        case 'translate':
            while True:
                translateToGreek()
                print('')
                print('Would you like to translate another phrase? [yes] [no]')
                print('')
                selection = input().strip().lower()
                print('')
                if selection != 'yes':
                    break
        case 'exit':
            print('')
            print('Thank you for using the Greek Practice Program!')
            print('')
            sys.exit()
        case _:
            print('')
            print('Please select either practice, translate, or exit')
            print('')

(You could probably pull the while True loops into their own dedicated functions, actually.)

Hope that helps. Feel free to ask if any of this doesn't make sense.

Even regular Rust code is more "exciting" than Python in this regard, since you have a choice between self, &self, and &mut self. And occasionally mut self, &'a self, and even self: Box<Self>. All of which offer different semantics depending on what exactly you're trying to do.

12

Found this article via a comment on Lobsters for a completely different article. It's not exactly the type of knowledge I see myself using in the immediate future, but I think it's still interesting and educational to think about.

17
submitted 1 year ago* (last edited 1 year ago) by shape_warrior_t@programming.dev to c/gamedev@programming.dev

Just wanted to publicly announce this project at this point in time. All the info is on the GitHub page. If you have any feedback, or find any bugs or issues, please let me know in the comments.

Interesting way of handling project vs global scope:

Some package managers (e.g. npm) use per-project scope by default, but also allow you to install packages globally using flags (npm install -g). Others (e.g. brew) use global scope.

I like the idea of allowing both project and global scope, but I do not like the flags approach. Why don't we apply a heuristic:

If there is a .sqlpkg folder in the current directory, use project scope. Otherwise, use global scope.

This way, if users don't need separate project environments, they will just run sqlpkg as is and install packages in their home folder (e.g. ~/.sqlpkg). Otherwise, they'll create a separate .sqlpkg for each project (we can provide a helper init command for this).

Seems rather implicit, though, especially if the command output doesn't specify which scope a package was installed in. If a user moves to a subdirectory, forgets they are there, and then tries to install a package, the package will unexpectedly install in global scope (though this particular version of the problem can be solved by also looking in parent directories).

Played a few rounds just to get an idea of what the game's like. Seems interesting. It took a while to actually figure out what controls were available and what the cards might do. Sometimes the stickmen would group up in the left corner and stay there for a good while, and there was seemingly nothing that could be done about it. The mechanism for scrolling felt a bit awkward to me, especially since the screen scrolled by such a large amount per click. Just some general notes about my experience, take them into account in whatever way best suits your game's vision.

15

An interesting article that lays out a problem and goes through a few different solutions, some of which I haven't thought about much before.

I'm working on a video game in Rust, and I'm running into this kind of modelling problem when it comes to keeping track of the game state. So far I've refactored something that resembles Approach 5 into something that looks more like Approach 3. As I get more confident about the final shape of the data, it (seemingly) becomes a better idea to represent it in a more structured way.

Can't resist pointing out how you should actually write the function in a "real" scenario (but still not handling errors properly), in case anyone wants to know.

If the list is guaranteed to have exactly two elements:

fn is_second_num_positive_exact(input: &str) -> bool {
    let (_, n) = input.split_once(',').unwrap();
    n.parse::<i32>().unwrap() > 0
}

If you want to test the last element:

fn is_last_num_positive(input: &str) -> bool {
    let n = input.split(',').next_back().unwrap();
    n.parse::<i32>().unwrap() > 0
}

If you want to test the 2nd (1-indexed) element:

fn is_second_num_positive(input: &str) -> bool {
    let n = input.split(',').nth(1).unwrap();
    n.parse::<i32>().unwrap() > 0
}
102

Thoughts? It does feel like there's a lot of things you can do in comments that would be impossible or impractical to do in names alone, even outside of using comments as documentation. There's certainly much more information that you can comfortably fit into a comment compared to a name.

One of the comments in the Lobste.rs post that I got this from stuck out to me in particular:

Funny story: the other day I found an old zip among my backups that contained the source code of game that I wrote 23 years ago. I was just learning to code at the time. For some reason that I forgot, I decided to comment almost every single line of that game. There are comments everywhere, even for the most obvious things. Later on, I learned that an excess of comments is actually not considered a good practice. I learned that comments might be a code smell indicating that the code is not very clear. Good code should be so clear, that it doesn’t need comments. So I started to do my best to write clear code and I mostly stopped writing comments. Doing so only for the very few parts that were cryptic or hacky or had a very weird reason for being there.

But then I found this old code full of comments. And I thought it was wonderful. It was so easy to read, so easy to understand. Then I contrasted this with my current hobby project, which I write on an off. I had abandoned it for quite some months and I was struggling to understand my own code. I’ve done my best to write clear code, but I wish I had written more comments.

And this is even worse at work, where I have to spend a ton of time reading code that others wrote. I’m sure the authors did their best to write clear code, but I often find myself scratching my head. I cherish the moment when I find some piece of code with comments explaining things. Why they did certain things, how their high level algorithm works, what does this variable do, why I’m not supposed to make that change that looks like it will simplify things but it will break a corner case.

So, I’m starting to think that this idea that comments are not such a good practice is actually quite bad. I don’t think I can remember ever reading some code and thinking “argh so many comments! so noisy” But, on the other hand, I do find myself often in the situation where I don’t understand things and I wish there were some more comments. Now I’m trying to write comments more liberally, and I think you should do the same.

I guess that’s a generalization of the op’s idea.

view more: next ›

shape_warrior_t

0 post score
0 comment score
joined 1 year ago