A little bit of neuroscience and a little bit of computing

  • 157 Posts
  • 2.26K Comments
Joined 1 year ago
cake
Cake day: January 19th, 2023

help-circle
  • 4. Any hard learnt lessons? Or tried and true tips?

    A basic lesson or tip from a discussion in this community (link here):

    PS: Abso-fucking-lutely just clone and don’t feel bad about it. Cloning is fine if you’re not doing it in a hot loop or something. It’s not a big deal. The only thing you need to consider is whether cloning is correct - i.e. is it okay for the original and the clone to diverge in the future and not be equal any more? Is it okay for there to be two of this value? If yes, then it’s fine.

    IE, using copy/clone as an escape hatch for ownership issues is perfectly fine.


    Another one that helps put ownership into perspective I think is this section in the Rustonomicon on unsafe rust, and the section that follows:

    There are two kinds of reference:

    • Shared reference: &
    • Mutable reference: &mut

    Which obey the following rules:

    • A reference cannot outlive its referent
    • A mutable reference cannot be aliased

    That’s it. That’s the whole model references follow.

    Of course, we should probably define what aliased means.

    error[E0425]: cannot find value `aliased` in this scope
     --> <rust.rs>:2:20
      |
    2 |     println!("{}", aliased);
      |                    ^^^^^^^ not found in this scope
    
    error: aborting due to previous error
    

    Unfortunately, Rust hasn’t actually defined its aliasing model. 🙀

    While we wait for the Rust devs to specify the semantics of their language, let’s use the next section to discuss what aliasing is in general, and why it matters.


    Basically it highlights that rust’s inferential understanding of the lifetimes of variables is a bit course (and maybe a work in progress?) … so when the compiler raises an error about ownership, it’s being cautious (as The Book stresses, unsafe code may not have any undefined behaviour).

    It helps I think reframe the whole thing as not being exclusively about correctness but just making sure memory bugs don’t happen


    Last lesson I think I’ve gained after chapter 4 was that the implementation and details of any particular method or object matter. The quiz in chapter 6 (question 5) I’ve mentioned is I think a good example of this. What exactly the Copy and Clone trait are all about too … where I found looking into those made me comfortable with the general problem space I was navigating in working with ownership in rust. Obviously the compiler is the safe guard, but you don’t always want to get beaten over with ownership problems.


  • 2. Any persistent gripes, difficulties or confusions?

    I’m not entirely sure why, but the whole Double-Free issue never quite sunk in from chapter 4. It’s first covered, I think here, section 4.3: Fixing an Unsafe Program: Copying vs. Moving Out of a Collection

    I think it was because the description of the issue kinda conflated ownership and the presence or absence of the Copy trait, which isn’t covered until way after chapter 4. Additionally, it seems that the issue mechanically comes down to whether the value of a variable is actually a pointer to a heap allocation or not (??)

    It was also a behaviour/issue that tripped me up in a later quiz, in an ownership recap quiz in chapter 6 where I didn’t pick it up correctly.

    Here’s the first quiz question that touches on it (see Q2 in The Book here, by scrolling down).

    Which of the following best describes the undefined behavior that could occur if this program were allowed to execute?

    let s = String::from("Hello world");
    let s_ref = &s;
    let s2 = *s_ref;
    println!("{s2}");
    

    For those not clear, the issue, if this code were permitted to execute, is that s2 would be a pointer to the same String that s points too. Which means that when deallocations occur as the scope ends, both s and s2 would be deallocated, as well as their corresponding memory allocations on the heap. The second such deallocation would then be of undefined content.

    I find this simple enough, but I feel like the issue can catch me whenever the code or syntax obscures that a pointer would be copied, not some other value, like in the re-cap quiz in chapter 6 that I got wrong and linked above.


  • If I had to explain ownership in rust (based on The Book, Ch 4)

    I had a crack at this and found myself writing for a while. I thought I’d pitch at a basic level and try to provide a sort core and essential conceptual basis (something which I think The Book might be lacking a little??)

    Dunno if this will be useful or interesting to anyone, but I found it useful to write. If anyone does find any significant errors, please let me know!

    General Idea or Purpose

    • Generally, the whole point is to prevent memory mismanagement.
    • IE “undefined behaviour”: whenever memory can be read/written when it is no longer controlled by a variable in the program.
      • Rust leans a little cautious in preventing this. It will raise compilation errors for some code that won’t actually cause undefined. And this is in large part, AFAICT, because its means of detecting how long a variable “lives” can be somewhat course/incomplete (see the Rustonomicon). Thus, rust enforces relatively clean variable management, and simply copying data will probably be worth it at times.

    Ownership

    • Variables live in, or are “owned by” a particular scope (or stack frames, eg functions).
    • Data, memory, or “values” are owned by variables, and only one at a time.
    • Variables are stuck in their scopes (they live and die in a single scope and can’t be moved out).
    • Data or memory can be moved from one owning variable to another. In doing so they can also move from one scope to another (eg, by passing a variable into a function).
    • Once a variable has its data/memory moved to another, that variable is dead.
    • If data/memory is not moved away from its variable by the completion of its scope, that data/memory “dies” along with the variable (IE, the memory is deallocated).
    // > ON THE HEAP
    
    // Ownership will be "moved" into this function's scope
    fn take_ownership_heap(_: Vec<i32>) {}
    
    let a = vec![1, 2, 3];
    take_ownership_heap(a);
    
    // ERROR
    let b = a[0];
    // CAN'T DO: value of `a` is borrowed/used after move
    // `a` is now "dead", it died in `take_ownership_heap()`;
    

    • Variables of data on the stack (eg integers) are implicitly copied (as copying basic data types like integers is cheap and unproblematic), so ownership isn’t so much of an issue.
    • Copying (or cloning) data/memory on the heap is not trivial and so must be done explicitly (eg, with my_variable.copy()) and in the case of custom types (eg structs) added to or implemented for that particular type (which isn’t necessarily difficult).
    // > ON THE STACK
    
    // An integer will copied into `_`, and no ownership will be moved
    fn take_ownership_stack(_: i32) {}
    
    let x = 11;
    take_ownership_stack(x);
    
    let y = x * 10;
    // NOT A PROBLEM, as x was copied into take_ownerhsip_stack
    

    Borrowing (with references)

    • Data can be “borrowed” without taking ownership.
    • This kind of variable is a “reference” (AKA a “non-owning pointer”).
    • As the variable doesn’t “own” the data, the data can “outlive” the reference.
      • Useful for passing a variable’s data into a function without it “dying” in that function.
    fn borrow_heap(_: &Vec<i32>) {}
    
    let e = vec![1, 2, 3];
    // pass in a reference
    borrow_heap(&e);
    
    let f = e[0];
    // NOT A PROBLEM, as the data survived `borrow_heap`
    // because `e` retained ownership.
    // &e, a reference, only "borrowed" the data
    
    • But it also means that the abilities or “permissions” of the reference with respect to the data are limited and more closely managed in order to prevent undefined behaviour.
    • The chief limitation is that two references cannot exist at the same time where one can mutate the data it points to and another can read the same data.
    • Multiple references can exist that only have permission to read the same data, that’s fine.
    • The basic idea is to prevent data from being altered/mutated while something else is reading the same data, as this is a common cause of problems.
    • Commonly expressed as Pointer Safety Principle: data should never be aliased and mutated at the same time.
    • For this reason, shared references are “read only” references, while unique references are mutable references that enable their underlying data to be mutated (AKA, mutable references).
      • A minor confusion that can arise here is between mutable or unique references and reference variables that are mutable. A unique reference is able to mutate the data pointed to. While a mutable variable that is also a reference can have its pointer and the data/memory and points to mutated. These are independent aspects and can be freely combined.
      • Perhaps easily understood by recognising that a reference is just another variable whose data is a pointer or memory address.
    • Additionally, while variables of data on the stack typically don’t run into ownership issues because whenever ownership would be moved the data is implicitly copied, references to such variables can exist and they are subject to the same rules and monitoring by the compiler.
    // >>> Can have multiple "shared references"
    
    let e_ref1 = &e;
    let e_ref2 = &e;
    
    let e1 = e_ref1[0];
    let e2 = e_ref2[0];
    
    // >>> CANNOT have shared and mutable/unique references
    
    let mut j = vec![1, 2, 3];
    
    // A single mutable or "unique" reference
    let j_mut_ref = &mut j;
    // can mutate the actual vector
    j_mut_ref[0] = 11;
    
    // ERROR
    let j_ref = &j;
    // CANNOT now have another shared/read-only reference while also having a mutable one (j_mut_ref)
    // mutation actually needs to occur after the shared reference is created
    // in order for rust to care, otherwise it can recognise that the mutable
    // reference is no longer used and so doesn't matter any more
    j_mut_ref[1] = 22;
    
    // same as above but for stack data
    let mut j_int = 11;
    let j_int_mut_ref = &mut j_int;
    // ERROR
    let j_int_ref = &j_int;
    // CANNOT assign another reference as mutable reference already exists
    
    *j_int_mut_ref = 22;
    // dereference to mutate here and force rust to think the mutable reference is still "alive"
    

    Ownership and read/write permissions are altered when references are created

    • The state of a variable’s ownership and read-only or mutable permissions is not really static.
    • Instead, they are altered as variables and references are created, used, left unused and then “die” (ie, depending on their “life times”).
    • This is because the problem being averted is multiple variables mangling the same data. So what a variable or reference can or cannot do depends on what other related variables exist and what they are able to do.
    • Generally, these “abilities” can be thought of as “permissions”.
      • “Ownership”: the permission a variable has to move its ownership to another variable or “kill” the “owned” data/memory when the variable falls out of scope.
      • “Read”: permission to read the data
      • “Write”: permission to mutate the data or write to the referenced heap memory
    • As an example of permissions changing: a variable loses “ownership” of its data when a reference to it is created. This prevents a variable from taking its data into another scope and then potentially “dying” and being deallocated for a reference to that memory to then be used and read or write random/arbitrary data from the now deallocated memory.
    • Similarly, a variable that owns its data/memory/value will lose all permissions if a mutable reference (or unique reference) is made to the same data/variable. This is why a mutable reference is also known as a unique reference.
    • Permissions are returned when the reference(s) that altered permissions are no longer used, or “die” (IE, their lifetime comes to an end).
    // >>> References remove ownership permissions
    
    fn take_ownership_heap(_: Vec<i32>) {}
    
    let k = vec![1, 2, 3];
    
    let k_ref = &k;
    
    // ERROR
    take_ownership_heap(k);
    // Cannot move out of `k` into `take_ownership_heap()` as it is currently borrowed
    let k1 = k_ref[0];
    // if the shared reference weren't used here, rust be happy...
    // as the reference's lifetime would be considered over
    
    // >>> Mutable reference remove read permissions
    
    let mut m = 13;
    
    let m_mut_ref = &mut m;
    
    // ERROR
    let n = m * 10;
    // CANNOT read or use `m` as it's mutably borrowed
    *m_mut_ref += 1;
    // again, must use the mutable reference here to "keep it alive"
    

    Lifetimes are coming

    fn first_or(strings: &Vec<String>, default: &String) -> &String {
        if strings.len() > 0 {
            &strings[0]
        } else {
            default
        }
    }
    
    // Does not compile
    error[E0106]: missing lifetime specifier
     --> test.rs:1:57
      |
    1 | fn first_or(strings: &Vec<String>, default: &String) -> &String {
      |                      ------------           -------     ^ expected named lifetime parameter
      |
      = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `strings` or `default`
    
    • In all of the above, the dynamics of what permissions are available depends on how long a variable is used for, or its “lifetime”.
    • Lifetimes are something that rust detects by inspecting the code. As stated above, it can be a bit cautious or course in this detection.
    • This can get to the point where you will need to explicitly provide information as to the length of a variable’s lifetime in the code base. This is done with lifetime annotations and are the 'as in the following code: fn longest<'a>(x: &'a str, y: &'a str) -> &'a str.
    • They won’t be covered here … but they’re coming.
    • Suffice it to appreciate why this is a problem needing a solution, with the code above as an example:
      • the function first_or takes two references but returns only one reference that will, depending entirely on runtime logic, depend on one of the two input references. IE, depending on what happens at runtime, one of the input references have a longer lifetime than the other. As Rust cannot be sure of the lifetimes of all three references, the programmer has to provide that information. A topic for later.





  • I’m not sold on the idea of “imperative programming without any shared mutable state at all”. Maybe I just can’t accurately imagine what that would look like in practice.

    Neither.

    for tasks that are not inherently unsuited to Rust’s current state

    Curious where you draw the line on this.

    This is the single “deepest” lesson that I feel like I’ve learned about Rust so far. Lifetimes, stumbled upon while trying to avoid (runtime) garbage collection, are in fact a more general tool for delimiting behavior/causality in programs.

    Interesting you use the word “causality” there … do you have anything in particular in mind or are you generally thinking about trying to nail down what the cause of a bug may be? I liked the phrase from the article (which you quote): you never need to worry about “spooky action at a distance.” (emphasis mine).



    • Do you think it would make sense to have a scheduled time or day during the week (or maybe every 2 weeks) for the post, kinda like a scheduled mega thread?
    • yea it’s challenging to get the right scope. Stealing from common sources, with maybe a bit of curation is probably best, so long as there’s some practicing happening and collaboration. Getting a running schedule for this again can help too I’d think.
    • something else to consider is that it will probably make sense, for those interested, to start digging into code bases (preferably lemmy IMO) in not too long a time. A number of collaborative things could be done here along those lines too. That is down the line though, so I’m just planting the seed (and thinking about it).

    How are you going with rust?


  • Fantastic framing! Not just of the internet, but the whole economic sector including big tech, various publishers, of course the ads industry and now all of the push for winning the AI platform wars.

    It’s a toilet economy! Fueled by the attention, tastes, inclinations and urges of people taking a shit! And now, as AI “learns” from the internet, also fed by and literally made of the writings and thoughts of people … taking a shit.

    It’s also a nice litmus test for what kind of internet space somewhere online is based on where people are when they comment or post: “Is this a toilet or desk space”. Depending on what you’re after, you will probably want to know if you’re in the right kind of place.






  • truth is that everything is scattered. And different alternative social media platforms or ecosystems … fighting and competing looks a bit silly once you zoom out a little. Both fediverse and BlueSky are sitting around 1 million monthly active users … which is nothing compared to the likes of twitter and threads and IG etc.

    It would be physically impossible to say that “all of the scientists are actually on BlueSky/Mastodon”. By any reasonable approximation, they’re all on Twitter/Threads, with some experimenting with alternative social media. And those few are likely on both because they’re still interested in getting their messages out there.





  • Ah right … makes sense. I don’t know how successful languages like Zig and Nim are but it’s interesting to see the energy around making a sort of modern C / C++ 2.0.

    My feeling around it all, however naive or unworkable, is always that real silver bullet is seamlessly composable features. Where in one ecosystem or “language” you can opt in to a borrow checker, or GC or ref counter or manual mem management, or dynamic or static typing etc … when and if you please. More and more new languages feels like it might be a dead end at a high level. In this respect, the “idea” of Mojo vaguely made sense to me (I never looked closely at it).


  • Likely annoying hot take (and certainly a rant)

    Being picky about bad CGI has run its course now and is likely a toxic urge in film culture ATM.

    I’m a bit of a broken record on this already (see prior posts here, here and here, all driven by the “No CGI is really just invisible CGI” series on YT.

    Is this actually helping us enjoy cinema or priming us to be sensitive to something that prevents us from enjoying something we easily could? All I’m going to say here is that some lessened sensitivity over the “quality” of CGI is likely warranted right now.

    And I know, we all prefer good CGI over bad so why not enjoy what we enjoy.

    Well because the industry is gaslighting us over how much CGI is actually everywhere and fundamental to modern cinema, likely in part because they enjoy pushing down the CGI industry, but also because they want to control what we think of as “spectacle” so that they control and retain effective marketing.

    Out of that YT series, there were two really novel things for me. One, was that the studio’s have always underplayed their reliance on effects and lied and not given VFX artists due credit almost since the beginning. Two, was that part of what’s going on right now with CGI and the excitement over “practical effects” is that the glorious epic spectacular shots that got viewers excited in the past have lost their appeal or efficacy due to over saturation over time.

    But spectacle puts people in seats and makes money. So the studios want to be able to tell us and control what is “good spectacle”.

    Sounds conspiracy theorist, I know. But I’m not talking about thought control here, just marketing.

    Think about how much you or I actually know about VFX and CGI?

    Do we really know what is “good” or “bad” CGI? Sure somethings will stand out to us as “bad”, but I’ve seen instances now of people mistaking “practical” for “bad CGI” for the simple reason that they don’t actually know what the practical thing really looks like (the Rings of Power trailer with liquid metal is I suspect a good example … people thought it was cheap CGI, but it was apparently practical … the point being that basically no internet nerd actually knows anything about what liquid metal looks like).

    Add to this how things and tastes have shifted pretty quickly as CGI has gotten way better pretty quickly, and you get a weird scenario where viewers can want the latest/best CGI to the point of being hyper-critical of “bad” CGI that would have been well received 10-20 years ago … while also demanding practical effects that “look real” when there’s a good chance that they’re either being lied to about what is real and isn’t and also don’t really know.

    But the studios want us hyped. So they’ll keep lying or trying to feed us what they think we want right now. And then viewers’ tastes will be molded by this experience. We’ll think we know what the latest/best CGI is and what “good and real” practical effects look like … which will push the next stage of attempts to hype us with lies and catering to our particular and likely somewhat arbitrary needs.

    It’s what got us to hyper-CGI driven film making in the 00s-10s and has got us studios lying now about practical effects that actually involve a lot of CGI (Top Gun seems really egregious on this front).

    And in all of this lack of transparency is a whole industry going unrecognised and being over-worked and underpaid by studios more likely to pretend they don’t exist than actually pay them for the work they do.

    So … maybe try to enjoy the story, characters and the writing?

    Maybe don’t be so obsessive about good/bad VFX? Maybe we no longer know what we’re talking about when it comes to convincing VFX, or at least spoilt to the point of being artistically meaningless in our amateur critiques?

    Maybe just break the hype feedback cycle

    /rant


  • Interestingly, this is what I liked about it. Sure, I was expecting more Sci-Fi, but I was pleasantly surprised to enjoy the psychological drama. They dragged it on too long and stumbled mid season, but I was there for it.

    As I saw it, it was about grief and loss, and that’s something not everyone is interested in watching all the time. So it was probably hard to market (unfortunately a lot of great things are just niche and hard to market). But the combination of a sci-fi idea and a drama about loss that they pulled off was for me rather wonderful.

    The idea of that liminal cabin and how they wander in and intersect there at various times was beautiful to me. Episode 6, personally, was one of the most touching pieces of TV I’d seen in a long time.