From: Eric Sunshine Date: Tue, 24 Mar 2026 03:33:35 GMT Subject: Re: [RFC] [GSoC]: STRBUF_INIT_CONST: initialize `strbuf` to constant string Message-ID: In-Reply-To: On Mon, Mar 23, 2026 at 1:11 AM Mateo Patino wrote: > On Sun, Mar 22, 2026 at 4:59 AM Eric Sunshine wrote: >> Although feedback to Robear Selwans's submission from some reviewers >> was subjective, Peff's review[*2*] pointed at a major roadblock; >> specifically, that strbuf has always promoted strbuf.buf is a >> writeable C-style string, so it is not safe simply to assign a pointer >> to a literal string to the "buf" member, and it's not practical to >> expect that all consumers of strbufs can be audited and modified to >> work correctly with the "new world order" that STRBUF_INIT_CONST would >> introduce. > > Since the Git codebase widely assumes strbuf.buf is writable, I wonder whether > we could create a new struct that is specifically documented as a read-only, > non-owning view into memory, something lightweight like `string_view` in C++, > which is an object that simply holds a pointer to a string in memory and the length. > For example, in C, > > struct strview { > const char *buf; > size_t len; > }; > > This struct would not care where the memory that `buf` points to exists. The > memory would be owned elsewhere and the caller would be responsible for > ensuring that the memory is valid throughout the lifetime of the struct. I think > this could help pass around string data without requiring ownership or > allocation, particularly in cases where the data is already available. > > A small downside I see to this approach is that we'd need to write a few helper > functions that accompany this struct, and they would likely share similar names > to the helper functions of `strbuf`, though I think this has been accepted in the > past in other places throughout the codebase. > > Another consideration is that this proposed `strview` would not address the > lifetime and ownership issue in [4], but having a safer way to pass read-only > strings seems like a step in the right direction. > > What do you think? I think this is a solution to a non-existent problem. Being written in C, Git does not (generally) have a need for this sort of structure. When Git code wants to "pass around" an immutable string to functions, those functions simply declare themselves as accepting a const string, as in: void do_something(const char *s) {...} In the less common case that the string is not NUL-terminated or only a portion of the string should be processed, the function also takes a length: void do_something(const char *s, size_t n) {...} This is a common idiom in the Git codebase, it's perfectly safe, doesn't involve ownership concerns, and there is no reason to stray away from it. The proposed `strview` is not safer and is probably not as convenient, thus adds no apparent value. But, having reread the threads which your initial email referenced, I think the bigger issue is that we're dealing with an XY Problem[1]. The original problem "X" being discussed was how to achieve static initialization of some string variables while still allowing the variables to be later pointed at heap-allocated memory, but at the same time avoiding memory leaks when those reassignments occur. The proposed solution "Y" was to somehow employ `strbuf` to solve X, however, it turns out that `strbuf` is utterly unsuitable for this use-case. Unfortunately, this "Y" proposal was then turned into a GitHub issue[2] which has led to this email thread as well as those aborted and misdirected submissions which you referenced earlier. If we take a step back and focus on the original problem rather than focusing on how to twist strbuf into something it was never meant to be, then a potential solution becomes clearer. Let's restate the original problem: static const char *global_var = "thimble"; void maybe_assign(const char **var, ...) { if (...some_condition...) { /* ??? free((void *)*var) ??? */ *var = some_heap_allocated_str; } } maybe_assign(&global_var, ...); ... maybe_assign(&global_var, ...); When maybe_assign() is called, it doesn't know whether or not the incoming `var` points at a static string literal ("thimble") or at some heap-allocated string, so it doesn't know whether or not to first free() `var` before assigning the new value. To solve this, we need a flag which indicates whether the string stored in the variable needs to be freed before the variable is reassigned. So, this suggests a dedicated, simple structure and a few related functions and a macro or two. For instance, something like this: struct str { char *s; int free_me; }; /* initialize `str` from a literal string (i.e. "foo") */ #define STR_INIT(X) { .s = (char *)(X), .free_me = 0 } void str_release(str *x) { if (x.free_me) FREE_AND_NULL(x.s); x.free_me = 0; } /* take ownership of a heap-allocated string */ void str_take(str *x, char * s) { str_release(x); x.s = s; x.free_me = 1; } /* assign a string literal (i.e. "foo") */ void str_assign(str *x, const char *s) { str_release(x); x.s = (char *)s; x.free_me = 0; } That's probably about all you need to solve the stated problem. Given the above, the original problem statement can be "fixed" by taking advantage of the above structure and functions: static struct str global_var = STR_INIT("thimble"); void maybe_assign(str *var, ...) { if (...some_condition...) str_assign(var, some_heap_allocated_str); } maybe_assign(&global_var, ...); Clients which need the value simply access the `.s` member directly. And there is no need to have any functions to morph the string in any way. If a client needs that functionality, it is easy enough to create and populate a proper `strbuf` from the `.s` member. [1]: https://xyproblem.info/ [2]: https://github.com/gitgitgadget/git/issues/398