Golang中的类型详解

Golang中的类型详解 大家好!我是Go语言的新手,正在尝试使用Fiber和MGM(MongoDB)构建一个简单的REST API。 我想了解变量是如何传递的。请看下面的函数…

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,
	})
}

重点关注这几行…

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

为什么大多数人不像下面这样写: if err := mgm.Coll(&models.Todo{}).Create(todo); err != nil { 既然这样是有效的?我的理解是 mgm.Coll 需要集合的“结构”来查找。

另外,为什么 if err := mgm.Coll(models.Todo).Create(todo); err != nil { 是无效的?难道类型本身不足以让函数确定结构吗?为什么我们需要用 &models.Todo{} 创建一个新实例?


更多关于Golang中的类型详解的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

谢谢!非常有帮助。 欢迎提供进一步的解释。

更多关于Golang中的类型详解的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


额外信息如下:

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,
	}
}

aceix:

既然 if err := mgm.Coll(&models.Todo{}).Create(todo); err != nil { 是有效的,为什么大多数人不这么写呢?我的理解是 mgm.Coll 需要集合的“结构”来查找。

这类似于变量的“显式类型声明”与“类型推断”之间的区别:显式类型声明明确地定义了每个变量的类型(例如 var todo *Todo = CreateTodo("title", "description")),而类型推断允许你只写 var todo = CreateTodo("title", "description") 或者更常见的是 todo := CreateTodo("title", "description"))。

有些人喜欢显式类型,因为每个表达式结果的类型都写在你面前,无需去查找每个函数返回什么。就我个人而言,我讨厌显式类型声明,我会选择 mgm.Coll(todo) 而不是 mgm.Coll(&models.Todo{})。如果我们想把 Todo 重命名为 ToDo 怎么办?你必须手动或用工具替换每一个出现的地方。如果将来 Todo 扩展了,出现了 DependentTodo 和/或 ExternalTodo 等类型,导致 CreateTodo 被改为返回一个接口怎么办?现在 mgm.Coll(&models.Todo{}) 会得到错误的类型,但如果你写 mgm.Coll(todo),类型推断就会选择正确的集合。

aceix:

另外,为什么 if err := mgm.Coll(models.Todo).Create(todo); err != nil { 是无效的?难道类型本身不足以让函数确定结构吗?为什么我们要用 &models.Todo{} 创建一个新实例?

另一个好问题:有些编程语言允许值和类型都用作函数、其他类型等的参数(例如,在类C语言中,“值参数”通常用圆括号 ( ) 括起来,而“类型参数”通常用“尖括号” < > 括起来)。Go只允许值作为参数,但为了绕过这个限制,你确实可以使用反射。

mgm.Coll 内部,它可能会检查其参数的类型,并返回它已经为该类型保存的特定集合信息,或者,如果这是第一次为该类型的实例调用 mgm.Coll,它会根据其参数类型生成所需的任何特定集合信息。

在Go语言中,mgm.Coll()方法需要接收一个实现了mgm.Model接口的具体实例指针,而不是类型本身。让我详细解释:

1. 为什么需要实例而不是类型

mgm.Coll()方法的签名通常是这样的:

func Coll(model Model) *Collection

它需要一个实现了Model接口的具体实例,因为:

  • 需要获取实例的集合名称(通过CollectionName()方法)
  • 可能需要访问实例的其他元数据
  • 类型本身(models.Todo)是一个类型标识符,不是值

2. 代码示例分析

你的代码中:

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

这里todo已经是一个具体的实例,所以可以直接传递给mgm.Coll()

3. 为什么这些写法不同

// 正确:传递todo实例(指针)
mgm.Coll(todo)

// 正确:创建新的零值实例
mgm.Coll(&models.Todo{})

// 错误:类型不是值
mgm.Coll(models.Todo)  // 编译错误

4. 实际示例

假设你的models.Todo结构如下:

package models

import "github.com/kamva/mgm/v3"

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

func (t *Todo) CollectionName() string {
    return "todos"
}

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

mgm.Coll()的实现大致如下:

func Coll(model Model) *Collection {
    // 需要调用实例的方法
    collectionName := model.CollectionName()
    
    // 需要反射获取类型信息
    modelType := reflect.TypeOf(model)
    
    // 这些都需要具体的实例,而不是类型
    return getCollection(collectionName, modelType)
}

5. 内存效率考虑

使用已存在的todo实例比创建新的&models.Todo{}更高效:

  • 避免额外的内存分配
  • 直接使用已有实例的元数据
  • 代码更简洁直观

所以你的原始写法是正确的,也是推荐的做法。只有当没有现有实例时,才需要使用&models.Todo{}创建新实例。

回到顶部