Re: [hackers] [quark] Reduce global state by localizing the server-struct || Laslo Hunhold

From: Laslo Hunhold <dev_AT_frign.de>
Date: Mon, 17 Aug 2020 14:44:21 +0200

On Mon, 17 Aug 2020 03:47:27 -0700
Jeremy <jer_AT_jer.cx> wrote:

Dear Jeremy,

thanks for your feedback. In any case, before diving into this topic,
st's maintainer (Hiltjo) has the final say on these things. It's not a
clear-cut debate and depends largely on personal taste and practicality.

> Then should the functions which use the global variable, "term", in
> st.c accept a Term pointer instead?
>
> In the case of st, I believe this may affect readability slightly,
> however, it would allow for some extensibility(if dvtm, for example,
> wanted to rewrite its terminal implementation).
>
> I'm not concerned about race conditions because the global "server"
> is copied via fork before any request-handling code has the chance to
> modify it.
>
> I agree with the changes, just not the reasoning - though I'd like to
> know what you think

The main motivation for me is to reduce side-effects and make clearer
what each part of the code works with. In pure functional programming
languages like Haskell, this is put to an extreme with the IO monad
(used as a "return value" of a), which reflects that a function does
any kind of IO. In Haskell, you thus can tell for every function what
it does exactly based on its surroundings, with the disadvantage that
you have a lot of memory allocations and garbage collection, which is
where Rust tries to go inbetween with its ownership model.

In C, you may control the input parameters and, if you avoid global
state, assume that the input parameters are the only thing you can
"work" with within the function, but what is done with those parameters
is another thing (i.e. side-effects by calling into libc or other
methods with side-effects).

To give an example: A function with the signature "max(int, int)" and
return type "int" can be expected to simply determine the maximum of
the two given integers. In Haskell, you can bake into the signature (by
not giving the IO-monad "IO int" as a return type) that it indeed only
returns an int and does not do any other things. In C, even though a
function is defined as "int max(int, int)", it can still call any
libc-method and nobody stops it from doing that.

All in all, while we can't formally control the side-effects of methods
in C, we can still be strict about the inputs and specify constness
where appropriate, which allows the compiler to warn us when we "break"
our own rules and make the code more readable.

------------------------------------------------------------------------

The use of global state in st makes a lot of sense, especially because
the terminal emulation and the drawing are so "close" to each other. On
the other hand, it sometimes makes the code harder to reason about, for
example

   void
   redraw(void)
   {
      tfulldirt();
      draw();
   }

might make you wonder what is actually needed for the redraw() (i.e.
what state is changed). In this case, diving into the code, you see
that tfulldirt() calls into tsetdirt(), which accesses term, and draw()
accesses term, win (via xstartdraw() and IS_SET), and you go down a long
rabbit hole with drawregion() calling xdrawline(), which really
accesses more or less all of the x-global-state. Thus, turning
everything into parameters would make it a big mess.

It may be noted that this can be solved with an architectural change,
e.g. an event loop that handles both the drawing-bit and
terminal-emulation, which both go asynchronously. What can be noted is
that redraws are only necessary when there are window-resize-events and
events relating the emulation (i.e. changes to the TTY). For the
benefit of a simpler architecture it might make sense to drop the
blinking-attribute (which requires explicit timing) and go with the
asynchronous approach that would, if I understand it correctly, drop
the need for explicit latency specifications.
Codewise, you would have local variables corresponding to term, win,
etc., but the draw-routines would get a "const"-pointer to term, as
they wouldn't affect the terminal emulation part.
However, such a deep restructuring would also warrant discussing
adopting EGL for the drawing part to become less dependent on X and
make it possible to run st in Wayland.

This has become a long WoT and I hope I could address your point to
your satisfaction.

With best regards

Laslo
Received on Mon Aug 17 2020 - 14:44:21 CEST

This archive was generated by hypermail 2.3.0 : Mon Aug 17 2020 - 14:48:34 CEST