Explanation of types in golang

Hello! New to golang and I’m trying out a simple REST API with Fiber and MGM (MongoDB).
I am to understand how variables are passed. Consider the function below…

func CreateTodo(ctx *fiber.Ctx) {
	params := new(struct {
		Title string
		Desc  string
	})

	ctx.BodyParser(&params)

	if len(params.Title) == 0 {
		ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"ok": false,
			"error": "Title not specified",
		})
		return
	}

	todo := models.CreateTodo(
		params.Title,
		params.Desc,
	)
	if err := mgm.Coll(todo).Create(todo); err != nil {
		ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"ok": false,
			"error": err.Error(),
		})
		return
	}

	ctx.JSON(fiber.Map{
		"ok": true,
		"todo": todo,
	})
}

Focusing on these lines…

todo := models.CreateTodo(
	params.Title,
	params.Desc,
)
if err := mgm.Coll(todo).Create(todo); err != nil {

why dont most people write
if err := mgm.Coll(&models.Todo{}).Create(todo); err != nil {
since that is valid? My understanding is the mgm.Coll needs the “structure” of the collection to find

also, why isnt if err := mgm.Coll(models.Todo).Create(todo); err != nil { valid? isn’t the type enough for the function to determine the structure? why do we create a new instance with &models.Todo{}?

Extra information here:

type Todo struct {
	mgm.DefaultModel `bson:",inline"`
	Title            string `json:"title" bson:"title"`
	Desc             string `json:"desc" bson:"desc"`
	Done             bool   `json:"done" bson:"done"`
}

func CreateTodo(title, desc string) *Todo {
	return &Todo{
		Title: title,
		Desc:  desc,
		Done:  false,
	}
}

This is like the distinction between “manifest typing” vs. “type deduction” for variables: Manifest typing explicitly defines the type of each variable (e.g. var todo *Todo = CreateTodo("title", "description")) where type deduction lets you write just var todo = CreateTodo("title", "description") or, more commonly, todo := CreateTodo("title", "description"))

Some people like the explicit types because the type of the result of every expression is written in front of you without you needing to lookup what each function returns. Personally, I hate manifest typing and would choose mgm.Coll(todo) over mgm.Coll(&models.Todo{}). What if we wanted to rename Todo to ToDo? You’d have to replace every occurrence either manually or with tooling. What if Todos get expanded in the future so that there are DependentTodos and/or ExternalTodos, etc., so that CreateTodo is changed to return an interface? Now mgm.Coll(&models.Todo{}) gets you the wrong type, but type deduction would have picked the right collection if you said mgm.Coll(todo).

Another good question: Some programming languages allow both values and types to be used as parameters to functions, other types, etc. (e.g. In C-like languages, “value parameters” are often enclosed in parentheses ((, )) and “type parameters” are often enclosed in “angle brackets” (<, >)). Go only allows values to be used as parameters, but to get around that limitation, you do get reflection.

Inside of mgm.Coll, it probably checks the type of its parameter and returns some collection-specific information it already has for that type, or, if it’s the first time mgm.Coll was called for an instance of that type, it generates whatever collection-specific information it needs about its parameter type.

2 Likes

Thank you! very helpful.
Any further explanation is welcome

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.