Re: [RFC] [GSoC]: STRBUF_INIT_CONST: initialize `strbuf` to constant string
- From
Eric Sunshine <sunshine@sunshineco.com>
- Date
- Mar 24, 2026, 03:33 UTC
- Message-ID
- <CAPig+cQcLJxxtsH0OeSP2DVUbSg8x95B-7n18fK9BVTJVywEtQ@mail.gmail.com>
- In-Reply-To
- <CAFRsFoV+k-8GMf=62GJwxP=o0Fy5RRBGW+h4NqOLjFbU6z96tw@mail.gmail.com>
On Mon, Mar 23, 2026 at 1:11 AM Mateo Patino <mateopatinodev@gmail.com> wrote:
Show 37 quoted lines
> On Sun, Mar 22, 2026 at 4:59 AM Eric Sunshine <sunshine@sunshineco.com> 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