Returning value or pointer struct for CRUD methods?

I know this has been debated a lot, but I am mostly finding very vague answers online. The consensus seems to be to use value return for small struct and pointer for large ones. However, mixing the style pretty much destroys design parity, so if one has to stuck with one type, which one will it be?

Consider this for the case where the app is a Web API, primarily responsible for CRUD operations on db tables with perhaps up to 20~30 columns (struct fields).

To be honest I always return pointers.
This allows me also to not initialize a struct in case of errors.

For instance a GET operation could look like

func (r *Repository) Get(id int) (*MyStruct, error) {
    var ret *MyStruct

    // check conditions
    if <conditions not met> {
        return nil, NotFound
    }
    ret = ....
    ret, nil
}

Same for Create, Update, …

2 Likes

code style

I usually always return structs, if there is no explicit reason to use a pointer. A pointer brings along additional headaches: Nil-Pointer panic, modification by other functions/routines and additional allocations/GC pressure.

One never has to reason about nil-checking the result of a function which returns a flat struct. When the function returns a pointer I have to read the documentation (and hope it’s up to date) to make sure there is no case where value and err could be nil.

Writing the function is straightforward either way - you don’t have to manually initialize the struct - if you declare a variable of that type it is automatically initialized. But if you use a pointer you have additional gotchas - you have to think about if you need to initialize it for example before passing it to json.Unmarshal or similar functions which accept a pointer.

performance

Foremost - if you don’t call these functions in tights loops or concurrently thousands of times the performance of structs vs. pointer will probably not matter at all - just choose whatever is more clear and idiomatic (no premature optimization)

But if you wonder what is more performant - it is probably almost always a plain struct. If the function is relatively small there is a high chance it will be inlined by the compiler - so your struct will not be copied at all but directly written to the stack. Even if you struct is copied, a copy on the stack is most likely faster than a memory allocation on the heap, because of memory locality and no garbage collection overhead.

That leaves the case of very big™ structs, because they may be expensive to copy and if the compiler decides they are to big for the stack they could escape to the heap - and in this case you would have an external allocation and copy of the struct, which is slower than a single allocation by using a pointer. You can check if a value escapes to the heap by running go build -gcflags=-m ...
Chances are low that your struct is big enough to trigger this escape (in the hundreds of bytes) since this only includes direct values and arrays in the struct. All maps and slices in your struct are references anyway and will not count to the size of the struct itself.

tl;dr

So in most cases you will actually get worse performance by using a pointer and will make the code a little more complex to read and write.

3 Likes

Most of the time, it won’t matter much. That’s why there is no consensus. Why doesn’t it matter?

For struct (or any type really) that the purpose is to represent a logic like AuthService or ProductRepository, or ProductListingHandler, Logger, their field usually don’t have any state, only a dependency. Here is an example of how does a struct called AuthService might look like:

type CredentialAccessor interface {
    GetUserByEmail(ctx context.Context, email string) (User, error)
}

type PasswordChecker interface {
    HashPassword(password string) string
    VerifyPassword(hashedPassword, password string) error
}

type AuthService struct{
    credentialAccessor CredentialAccessor
    passwordChecker    PasswordChecker
}

func NewAuthService(
    credentialAccessor CredentialAccessor, 
    passwordChecker    PasswordChecker,
) AuthService { // or you can also return *AuthService
   ...
}

As you can see, all the field in the AuthService doesn’t manage any state. The value of credentialAccessor, nor passwordChecker will never change. It most probably injected when the program run for the first time, and will stay there forever.
Because of this, you can return AuthService (not *AuthService) and expect it to just run. If you change it to *AuthService it will also run correctly. Is there any performance overhead? Maybe there is, if you return *AuthService, you might trigger a heap allocation, but it won’t matter. Because in the end of the day, you might cast AuthService to an interface and trigger the allocation anyway. All the field in the AuthService are also already wrapped as an interface, which might already trigger the allocation. On top of that, this allocation is so small, and only happen once. It’s so small, that you won’t even notice it.

If you need to maintain state, it would be wise to return a pointer, because you might not want to have a splitted state, where you pass the struct, modify it, but the caller don’t get the modification.

If your struct represent a data like User or Response, it’s a different story. You might want your struct to be immutable. In this case, returning pointer can be bad. Also, if it represent data, it will be allocated a lot and returning a pointer might escapes your struct to heap which can be bad for performance.

Now, in the above paragraphs, I talk about correctness and performance overhead. There is also argument about style. But style is very subjective. I believe you have your own style as well. In this case, just follow your style because it doesn’t matter for anybody else. If you work on a team, just agree on one style, and follow it.