When you come from C++ world, you already knew std::enable_if concept. To have the same logic in Go, we need to do some development as there is no straight way to handle it.

What exactly it does in C++?

std::enable_if is a standard library to enable methods or functions based on predefined conditions. Official definition is:

template<bool B, class T = void>
struct enable_if;

If B is true, std::enable_if has a public member typedef type, equal to T; otherwise, there is no member typedef.

Let’s see a sample code to grasp the concept.

Imagin you have 3 types of data structure: Car, Bike, and Walk, and you want to have a method like Repair, “if” types are Car and Bike. Is it the place where std::enable_if comes:

#include <iostream>
#include <string>
#include <type_traits>

// Custom data structures.
struct Car{
    std::string name;
};
struct Bike{
    std::string name;
};
struct Walk{
    std::string name;
};

// Custom traits.
template <typename T>
struct IsVehicle {
  static const bool value = false;
};

template <>
struct IsVehicle<Car> {
  static const bool value = true;
};

template <>
struct IsVehicle<Bike> {
  static const bool value = true;
};

// Enable function if T is Car or Bike.
template <class T>
typename std::enable_if<IsVehicle<T>::value>::type Repair(T data_type) {
    std::cout << "Repairing has started..." << data_type.name << std::endl;
}

int main() {
    auto data_car = Car{name:"c1"};
    Repair(data_car);

    auto data_bike = Bike{name:"b1"};
    Repair(data_bike);

    auto data_walk = Walk{name:"w1"};
    Repair(data_walk);
}

When we try to compile it, it will raise an error because there is no “Reapir” function defined for Walk type:

$ g++ code.cpp

code.cpp: In function ‘int main()’:
code.cpp:46:11: error: no matching function for call to ‘Repair(Walk&)   46 |     Repair(data_walk);
      |     ~~~~~~^~~~~~~~~~~
code.cpp:34:52: note: candidate: ‘template<class T> typename std::enable_if<IsVehicle<T>::value>::type Repair(T)   34 | typename std::enable_if<IsVehicle<T>::value>::type Repair(T data_type) {
      |                                                    ^~~~~~
code.cpp:34:52: note:   template argument deduction/substitution failed:
code.cpp: In substitution of ‘template<class T> typename std::enable_if<IsVehicle<T>::value>::type Repair(T) [with T = Walk]’:
code.cpp:46:11:   required from here
code.cpp:34:52: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’

The error is clear what happened exactly. This is very important to know that it happpend at compile time!

Now, as you got the general idea, we want to implement the same in Go!

Go implementation

In Go, there is no direct way exist to handle it. But, we can make our own one. It is important to know that std::enable_if in C++ is useful for different scenarios and supports different parameters and flexibility, but in Go we can’t handle all the scenarios and parameters and we should only implement based on requirements.

So, it this scenario, we only want to enable a function only for vehicles, the same as C++ implementation.

package main

import (
	"fmt"
	"reflect"
	"runtime"
	"strings"
)

// Custom data structures.
type Car struct {
	Name string
}
type Bike struct {
	Name string
}
type Walk struct {
	Name string
}

type Fn func(data any)

func EnableIf(fn Fn) Fn {
	return func(data any) {
		typeName := reflect.TypeOf(data).Name()
		// Source: https://stackoverflow.com/a/7053871
		fnFullPath := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
		fnName := strings.Split(fnFullPath, ".")
		if typeName != "Car" && typeName != "Bike" {
			panic(fmt.Sprintf("error: no matching function for call to ‘%s(%s)’", fnName[len(fnName)-1], typeName))
		}
		fn(data)
	}
}

func Repair(data any) {
	fmt.Println("Repairing has started...", data)
}

func main() {
	fn := EnableIf(Repair)

	data_car := Car{Name: "c1"}
	fn(data_car)

	data_bike := Bike{Name: "b1"}
	fn(data_bike)

	data_walk := Walk{Name: "w1"}
	fn(data_walk)
}

Now, let’s execute the code to see the result:

$ go run ./code.go

Repairing has started... {c1}
Repairing has started... {b1}
panic: error: no matching function for call to ‘Repair(Walk)
goroutine 1 [running]:
main.EnableIf.func1({0x490640, 0xc000014280})
	/home/mort/code.go:30 +0x225
main.main()
	/home/mort/code.go:50 +0xd9
exit status 2

As source code and result demonstrate, it’s a runtime validation, while in C++ it’s compile-time validation. The important thing for us was the general idea of how to implement enable_if in Go.

It was the whole idea to talk about what is enable_if concept and how to achieve it in Go. I know it’s not a clean way to implement an idea in Go, but let’s consider it just as an idea.