Hacker News new | ask | show | jobs
by henesy 1574 days ago
Author here.

While the other comments are correct, the exact reason for this use of providing in/out is that the reversal is called on a subset of the incoming array.

  line = Brdstr(in, '\n', 1);
will give us a null-terminated string, but we don't want to flip the null and truncate the string, so to lazily avoid that we do:

  rlen = runestrlen(rstr);
  rev = calloc(rlen+1, sizeof (Rune));
  reverse(rstr, rev, rlen);
so we get the number of runes in the input, add 1 for the \0, then reverse the pre-\0 characters.

We could have the reverse() function allocate n+1 elements for the string and return an always null-delimited string, but then we need to pass it a string that doesn't have a \0, or make it assume that it will always get a \0 and treat that some way.

Passing in both items and the number to iterate felt less noisy for a quick solution :)

1 comments

Thank you, but that's still not an answer to my question :)

You pass in *out as an argument (allocated by caller, so caller has a handle on it), then the result/value of reverse ends up in *out - and then reverse returns *out as a return value in addition to having done its work.

I was wondering why (I guess you could say I ask why it's a "function" not a "procedure").

I guess that the contract is that "reverse" takes ownership of *out as its passed in, and just happens to not return a pointer to a different buffer. But then my question is why it can't do its own allocation too... (which you did explain).

In other words - why does reverse return a pointer rather than, say a success/error (or void - can it error? Maybe on arbitrary binary data?)

The first version I implemented did allocate and return the allocated output buffer, but I scrapped that design

I never changed the return value after I did this and didn’t see an issue at the time

For the record, this program was written casually on a live stream and is hardly a textbook definition of correctness :)

Ok, thank you. I didn't mean it as criticism, just genuinely curious about the (c language) idioms involved - especially after struggling a bit with Zig strings in relation to advent of code - and after seeing the approach rust takes on ownership.

In zig the idiomatic approach (as far as I can tell) is to manage allocator in main/the caller, and passing that down to the callee. Thus allocation strategy (heap/stack/arena etc) is global/caller determined, while resources are "locally" managed.

I generally work in high level languages like ruby, where one rarely worries about the details of (especially string) allocation (beyond trying to not create way too many copies/buffers).

So it's interesting to see what kind of balance on encapsulation/delegation of this concern can/should be found in C.

Thank you for taking the time to comment.

The other comments are correct that there's value in returning a value even if it was passed in to make chaining functional calls easier.

This can get a little tricky when ownership comes in to play since you don't want to end up in a situation where (not in the above program's situation, but in general) the caller might pass out a value that never gets freed and you can easily create memory leaks.

You wouldn't want to do, for example:

  print("%s\n", smprint("%s", L"世界"));
Since smprint(2)'s return value was allocated and will need to be freed, but we have no way of doing that after the value is passed.

You'd need to do:

  char *buf = smprint("%s", L"世界");
  print("%s\n", buf);
  free(buf);