...

kannanvijayan

1289

Karma

2010-08-10

Created

Recent Activity

  • Not sure how seasoned I am, but I reject any comparison to a cooking utensil!

    I do find myself running into lifetime and borrow-checker issues much less these days when writing larger programs in rust. And while your comment is a bit cheeky, I think it gets at something real.

    One of the implicit design mentalities that develops once you write rust for a while is a good understanding of where to apply the `UnsafeCell`-related types, which includes `Arc` but also `Rc` and `RefCell` and `Cell`. These all relate to inner mutability, and there are many situations where plopping in the right one of these effectively resolves some design requirement.

    The other idiomatic thing that happens is that you implicitly begin structuring your abstract data layouts in terms of thunks of raw structured data and connections between them. This usually involves an indirection - i.e. you index into an array of things instead of holding a pointer to the thing.

    Lastly, where lifetimes do get involved, you tend to have a prior idea of what thing they annotate. The example in the article is a good case study of that. The author is parsing a `.notes` file and building some index of it. The text of the `.notes` file is the obvious lifetime anchor here.

    You would write your indexing logic with one lifetime 'src: `fn build_index<'src>(src: &'src str)`

    Internally to the indexing code, references to 'src-annotated things can generally pass around freely as their lifetime converges after it.

    Externally to the indexing code you'd build a string of the notes text, and passing a reference to that to the `build_index` function.

    For simple CLI programs, you tend not to really need anything more than this.

    It gets more hairy if you're looking at constructing complex object graphs with complex intermediate state, partial construction of sub-states, etc. Keeping track of state that's valid at some level, while temporarily broken at another level, is where it gets really annoying with multiple nested lifetimes and careful annotation required.

    But it was definitely a bit of a hair-pulling journey to get to my state of quasi-peace with Rust's borrow checker.

  • There's a risk of misaligned perspectives here.

    What you've called out is meaningful, but it includes an implied requirement that you're not spelling out: the time and effort and attention to detail, along with the history of experience, to really apply the principles you are talking about to a project.

    When I read the main article, I interpreted it from my perspective. I'm mostly a systems person. I can appreciate the points you mentioned, but I don't have those other implied things that are prerequisites for applying those concepts effectively.

    Without rules of thumb like these, the UIs I design end up looking like grade school collages. Building UIs that make people _feel good_ when using them is not my core competency. I'm just looking for a baseline level of quality.

    I believe there are two cohorts here: the people who want to make UIs that are beautiful, and the people who need to make UIs that are not hot garbage.. and we're reading the article from our respective perspectives.

  • Hi Alon! It's been a while.

    Can't bounds checks be avoided in the vast majority of cases?

    See my reply to nagisa above (https://news.ycombinator.com/item?id=45283102). It feels like by using trailing unmapped barrier/guard regions, one should be able to elide almost all bounds checks that occur in the program with a bit of compiler cleverness, and convert them into trap handlers instead.

  • I don't feel this is going to be as big of a problem as one might think in practice.

    The biggest contributor to pointer arithmetic is offset reads into pointers: what gets generated for struct field accesses.

    The other class of cases are when you're actually doing more general pointer arithmetic - usually scanning across a buffer. These are cases that typically get loop unrolled to some degree by the compiler to improve pipeline efficiency on the CPU.

    In the first case, you can avoid the masking entirely by using an unmapped barrier region after the mapped region. So you can guarantee that if pointer `P` is valid, then `P + d` for small d is either valid, or falls into the barrier region.

    In the second case, the barrier region approach lets you lift the mask check to the top of the unrolled segment. There's still a cost, but it's spread out over multiple iterations of a loop.

    As a last step: if you can prove that you're stepping monotonically through some address space using small increments, then you can guarantee that even if theoretically the "end" of the iteration might step into invalid space, that the incremental stepping is guaranteed to hit the unmapped barrier region before that occurs.

    It's a bit more engineering effort on the compiler side.. and you will see some small delta of perf loss, but it would really be only in the extreme cases of hot paths where it should come into play in a meaningful way.

  • No, I don't think it will. Pointers to managed objects are opaque, and aren't actually backed by the wasm memory buffer. The managed heap is offloaded.

    Shrinking the memory object shouldn't require any special support from GC, just an appropriate API hook. It would, as always, be up to the application code running inside the module to ensure that if a shrink is done, that the program doesn't refer to memory addresses past the new endpoint.

    If this hasn't been implemented yet, it's not because it's been waiting on GC, but more that it's not been prioritized.

HackerNews