So let me be very clear: if you as a maintainer feel that you control who or what can use your code, YOU ARE WRONG.

  • @solrize
    link
    131 day ago

    Longjmp? Not that the kernel uses that ofc.

    • @[email protected]
      link
      fedilink
      5
      edit-2
      14 hours ago

      Begone, vile creature, use call/cc if you’re into that kind of stuff. What you’re doing might be consensual but it sure ain’t safe or sane. Unless you’re implementing call/cc, then I forgive you, and extend my condolences.

      • @solrize
        link
        412 hours ago

        Longjmp in C is generally used for implementing exceptions or things of that sort, which is fine. Even Ada and Haskell have exceptions. C++ has them too, though maybe that doesn’t speak in their favor as much. Rust lacks them, but so far that seems to me to be a shortcoming in Rust. Even those wanting unwinding of error value checking through the entire call stack could look at the C++ deterministic exception proposal which is similar to Haskell’s ErrorT monad transformer.

        This is worth reading about Haskell if such topics are of interest: https://research.microsoft.com/en-us/um/people/simonpj/papers/marktoberdorf/mark.pdf

        Anyway, Rust has its own sort of call/cc thing as far as I can tell (not sure), in its async runtimes. There is a coroutine switching scheme underneath that, amirite? Where do the call stacks for the different async tasks live and how are they allocated? I’ve been wondering, “Comprehensive Rust” doesn’t go into any detail about this.

        Call/cc in its most general form is possibly evil, but delimited continuations, the most common use of call/cc, are perfectly cromulent as far as I know. My brain is not currently big enough to understand the topic but I’m going by some of Oleg Kiselyov’s old writings, probably linked from here: https://okmij.org/ftp/continuations/index.html

        • @[email protected]
          link
          fedilink
          5
          edit-2
          11 hours ago

          async is stackless coroutines, less powerful than stackful ones and vastly less powerful than first-class continuations, which is what call/cc provides, but also way more performant as there’s basically zero memory management overhead…

          Where do the call stacks for the different async tasks live and how are they allocated?

          That’s the neat thing: They aren’t, futures don’t contain their stack the compiler infers, statically, what they will need, and put that in a struct. As said, practically zero memory overhead.

          First-class continuations aren’t evil they’re naughty, dirty only if used when not actually making anything clearer. Delimited continuations are actually a generalisation of call/cc and arguably way easier to think about, it’s just that call/cc predates their discovery and made it into standard lisp and scheme, where is that Felleisen paper, here: 1988. 13 years after scheme, 28 after lisp. longjmp/setjmp exist since at least System V and C89 according to the linux man pages, I guess they implemented it to implement lisp in. It’s just that C is about the worst language to use when you’re writing anything involving continuations, everything gets very complicated very fast, and not because continuations are involved but because you have no gc, no nothing, to support you.

          • @solrize
            link
            3
            edit-2
            5 hours ago

            Scheme has call/cc but standard Lisp (i.e. Common Lisp) doesn’t. Hmm, Rust async is like C++20 coroutines, so they can only yield from the outermost level of the task etc.? (Added: C Protothreads also come to mind). That sounds constraining even compared to the very lightweight Forth multitaskers of the 1970s. Python’s original generators were like that, but they fixed them later to be stackful coroutines. ~And do you mean there is something like a jump table in the async task, going to every possible yield point in the task, such as any asynchronous i/o call? That could be larger than a stack frame, am I missing something?~ (No that wouldn’t be needed, oops).

            Setjmp/longjmp in C had some predecessor with a different name, that went all the way back to early Unix, way before C89. It was routinely used for error handling in C programs.

            I’ve implemented Lisp (without call/cc) in C using longjmp to handle catch/throw. It wasn’t bad. Emacs Lisp also works like that.

            I had been pretty sure that delimited continuations were strictly less powerful than first-class continuations, but I’ll look at the paper you linked.

            I found some search hits saying you were supposed to implement signal handlers in Rust by setting a flag in the handler and then checking it manually all through the program. Even C lets you avoid that! It sounds painful, especially when there can be asynchronous exceptions such as arithmetic error traps (SIGFPE) that you want to treat sanely. But I haven’t looked at any Rust code that does stuff like that yet.

            Hmm, I wonder if it’s feasible to use the Boehm garbage collection method in Rust, where the unsafe region is limited to the GC itself. Of course it would use pointer reversal to avoid unbounded stack growth. Of course I don’t know if it’s feasible to do that in Ada either.