3

Take the following signature

BigInt* addBigInt(BigInt* arg1, BigInt* arg2);

Traditionally, the safest way to implement this function would be for it not mutate the arguments, and would necessarily need to create a new object to result for the operation. Alternatively, if the aim is to conserve on system memory, arg1 could be the object that the resulting operation would return having mutated that object.

The question is, is there an established naming convention the would delineate the nuance between the two implementation choices? Or is this something that would need to be clearly laid out in the documentation where the developer is establishing the convention?

Let's assume this is in C where function overloading is not possible.

2
  • 1
    I know of some other langauges that have conventions on this. Ruby uses ! at the end of methods that operate in place (e.g. array.sort! vs result = array.sort), though you can't do that in C given that ! isn't a valid character for an identifier name. I like Swift's convention though, "use the verb’s imperative for the mutating method and apply the “ed” or “ing” suffix to name its nonmutating counterpart." E.g. array.sort() vs let result = array.sorted() swift.org/documentation/api-design-guidelines/…
    – Alexander
    Commented Feb 27, 2021 at 22:51
  • This is not relevant to the question, but I don't think you would want to implement addBigInt this way, assuming the application is calculation-heavy. Performing a heap allocation for every addition will quickly cause a bottleneck. And safety of such function is also questionable, you need to remember every time to free the allocated object. On the other hand, addBigInt(*dest, *src1, *src2) can us variables on the stack or managed memory. Commented Apr 1 at 7:33

5 Answers 5

4

If you create a new object, return it.

public int[] square(int a[], int size)
{
   int b[] = malloc(size * sizeof(int));

   for (int i=0; i<size; i++)
       b[i] = a[i] * 2;

   return b;
}

If you don't create an object, return void.

public void square(int a[], int size)
{
   for (int i=0; i<size; i++)
       a[i] = a[i] * 2;
}

Note that programming languages that support method overloading don't usually distinguish between return types. If you really need both functions, just change the name of one of them; perhaps square and square_copy.

4
  • 1
    Though this is a fine convention, note that most C functions like strncat() do not follow it. They name arguments source and destination for clarity.
    – user949300
    Commented Feb 28, 2021 at 6:01
  • @user949300: Concatenation is not the same as creating a brand new array. strncat modifies the destination string in-place. That's why it returns void. Commented Feb 28, 2021 at 15:03
  • 1
    strncat both modifies the destination in place, it also returns it. Which is arguably confusing, as your void convention is clearer. However, as a "standard", it might be what many C programmers would expect.
    – user949300
    Commented Feb 28, 2021 at 17:50
  • 1
    @user949300: Ah, I see that. Apparently I've been discarding the return value all this time. Commented Feb 28, 2021 at 18:50
1

In C, you'd use const to indicate that a pointer argument is not mutable. Using your addBigInt example, if you want to indicate that neither *arg1 nor *arg2 are mutable, you'd write

 BigInt *addBigInt( const BigInt *arg1, const BigInt *arg2 );

Any attempt to write to *arg1 or *arg2 in addBigInt should cause the compiler to issue a diagnostic (even if the actual arguments arg1 and arg2 point to are not const). If you were going to write the function such that the result is stored in *arg1, you could do something like

BigInt *addBigInt( BigInt *arg1, const BigInt *arg2 );

You'd just return arg1 rather than a new object. However, that's not perfectly clear just from the signature - you'd want to add some documentation:

/**
 * Adds two BigInts together, result is stored in the object
 * pointed to by arg1
 *
 * Inputs:
 *    arg1 
 *    arg2 
 *
 * Outputs:
 *    arg1
 *
 * Return value: 
 *    arg1
 */

Note that const doesn't necessarily mean "put this thing in read-only memory" (although that may be the result), it just means "this thing is not meant to be written to, and flag any code that attempts to do so."

Basic rules of const and pointers:

 const T *p;  // p is a non-const pointer to const T; you can write to p, but not *p
 T const *p;  // same as above

 T * const p; // is a const pointer to non-const T; you can write to *p, but not p
 
 const T * const p; // p is a const pointer to const T; you cannot write to either p or *p
 T const * const p; // same as above
0

Now I have never thought about trying to find naming convention about that. But If the method is void we can speculate that it may be a Procedure.

If I am to name a procedure I would focus more on describing what it does. In my opinion, it is more common for a Procedure to do more than one thing compared to a function and then a procedure would benefit from a more descriptive name even if it does not start with a verb.

0

If you want to make it super explicit that a function modifies its argument(s), you might name it inplace_something() or something_inplace() so the intention becomes apparent without looking at argument or result types which may not be immediately visible in the context of the call.

0

As I mentioned in the comment, it's not actually a good idea to return pointer to an allocated object from any old function. Using your example, I'd use the naming convention for a constructor:

// we us a named constructor _fromSum,
// since probably this is not the only
// constructor like this
BigInt * bigInt_fromSum_new(BigInt const * arg1, BigInt const * arg2);

This way you can be reasonably certain that you or anyone else using your code will know to call bigInt_free(BigInt *); after they are done with the object.

Moving away from the example and into a real code, if BigInt requires some special construction before use, I'd actually only define two functions manipulating memory:

BigInt * bigInt_new();
// we pass pointer to pointer,
// so we don't need to remember
// to set pointer value to NULL
// after every free
void bigInt_free(BigInt **);

and define all other functions with assumption that all arguments are already pre-allocated. It can also be helpful to define void bigInt_init(BigInt*) to allow allocation of many BigInt-s in a block memory with one malloc and initialize each instance in that pre-allocated memory.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.