make是不是go的关键字

发布时间 2023-11-09 20:40:33作者: tsecer

keyword

go语言介绍中标榜的一个重要特点是语法简单,这里有一个不同语言关键字的个数,同样是为了防止网页打不开或者丢失,这里单独复制一份:

  • C (ANSI (C89)) (32 keywords)
  • C (C11) (44 keywords)
  • C (C17) (44 keywords)
  • C (C99) (37 keywords)
  • C# (8.0) (107 keywords)
  • C++ (C++03) (74 keywords)
  • C++ (C++11) (84 keywords)
  • C++ (C++14) (84 keywords)
  • C++ (C++17) (84 keywords)
  • C++ (C++20) (92 keywords)
  • C++ (C++98) (74 keywords)
  • Dart (2.2) (33 keywords)
  • Elixir (1.10) (15 keywords)
  • Erlang (23) (27 keywords)
  • Fortran (Fortran 2008) (103 keywords)
  • Go (1.18) (25 keywords)
  • Java (SE 11 LTS) (51 keywords)
  • Java (SE 17 LTS) (67 keywords)
  • JavaScript (1st edition) (35 keywords)
  • JavaScript (2nd edition) (59 keywords)
  • JavaScript (3rd edition) (59 keywords)
  • JavaScript (5th edition) (45 keywords)
  • JavaScript (6th edition) (46 keywords)
  • Kotlin (1.4) (79 keywords)
  • Lua (5.3) (22 keywords)
  • MATLAB (R2020a) (20 keywords)
  • Objective-C (2.0) (85 keywords)
  • PHP (7.4) (69 keywords)
  • Python 2 (2.7) (31 keywords)
  • Python 3 (3.10) (38 keywords)
  • R (4.0) (21 keywords)
  • Ruby (2.7) (41 keywords)
  • Rust (1.46) (53 keywords)
  • Scala (2.13) (40 keywords)
  • Swift (5.3) (97 keywords)
  • Visual Basic (2019) (217 keywords)

从这个数据中看:Go只有25个关键字,跟C# 100+的关键字数量相比的确少了很多。这些关键字分别是

The following keywords are reserved and may not be used as identifiers.

break        default      func         interface    select
case         defer        go           map          struct
chan         else         goto         package      switch
const        fallthrough  if           range        type
continue     for          import       return       var

如果只是看这些关键字其实也平平无奇,但是仔细看会发现其中并没有包含我们非常感兴趣、比较特殊、并且经常使用的make、new这两个关键字,尽管很多Go的介绍中都会提到这两个操作。

这两个操作符另一个重要的特点它们接受的参数是类型而不是数值,它的语法类似于C++中的模板。在编译器中,表达式和类型是两种不同的语法类型,需要做特殊处理。

Built-in functions

如果再找找看会发现,make/new(以及一些其他的)“关键字”是被列入到了Built-in functions这个分类中。

Built-in functions are predeclared. They are called like any other function but some of them accept a type instead of an expression as the first argument.

The built-in functions do not have standard Go types, so they can only appear in call expressions; they cannot be used as function values.

答案

StackOverflow上的合理解释

In short: because using predeclared identifiers in your own declarations don't make your code ambiguous.

回答者提供的示例代码

make := func() string {
    return "hijacked"
}

int := make()    // Completely OK, variable 'int' will be a string
fmt.Println(int) // Prints "hijacked"

讨论列表

官方作者的部分解释也从另一个侧面说明了make/new是两个比较特殊的函数

When we fixed up the grammar to avoid needing
a symbol table during the parse, one of my goals
was to make make and new not special, exactly
so that we could pass types to other things in
the future, like the case you've mentioned or some
unspecified form of generics.

gcc视角

为什么编译器可以声明而用户程序不能

简言之,编译器跳过了语法解析,直接将函数声明“注入”到了符号表中,而用户定义的函数必须经过编译器的语法解析,编译器不提供该语法功能就是无法实现。

可以看到,在函数声明中还声明了函数变参,也就是可以接受任意多参数。

// Class Gogo.

Gogo::Gogo(Backend* backend, Linemap* linemap, int, int pointer_size)
  : backend_(backend),
  ///...
    const Location loc = Linemap::predeclared_location();

  Named_type* uint8_type = Type::make_integer_type("uint8", true, 8,
						   RUNTIME_TYPE_KIND_UINT8);
  this->add_named_type(uint8_type);
  this->add_named_type(Type::make_integer_type("uint16", true,  16,
					       RUNTIME_TYPE_KIND_UINT16));
  this->add_named_type(Type::make_integer_type("uint32", true,  32,
					       RUNTIME_TYPE_KIND_UINT32));
  this->add_named_type(Type::make_integer_type("uint64", true,  64,
					       RUNTIME_TYPE_KIND_UINT64));

  this->add_named_type(Type::make_integer_type("int8",  false,   8,
					       RUNTIME_TYPE_KIND_INT8));
  this->add_named_type(Type::make_integer_type("int16", false,  16,
					       RUNTIME_TYPE_KIND_INT16));
  Named_type* int32_type = Type::make_integer_type("int32", false,  32,
						   RUNTIME_TYPE_KIND_INT32);
  this->add_named_type(int32_type);
  this->add_named_type(Type::make_integer_type("int64", false,  64,
					       RUNTIME_TYPE_KIND_INT64));

  this->add_named_type(Type::make_float_type("float32", 32,
					     RUNTIME_TYPE_KIND_FLOAT32));
  this->add_named_type(Type::make_float_type("float64", 64,
					     RUNTIME_TYPE_KIND_FLOAT64));
///...
  this->globals_->add_constant(Typed_identifier("true",
						Type::make_boolean_type(),
						loc),
			       NULL,
			       Expression::make_boolean(true, loc),
			       0);
  this->globals_->add_constant(Typed_identifier("false",
						Type::make_boolean_type(),
						loc),
			       NULL,
			       Expression::make_boolean(false, loc),
			       0);

  this->globals_->add_constant(Typed_identifier("nil", Type::make_nil_type(),
						loc),
			       NULL,
			       Expression::make_nil(loc),
			       0);

  Type* abstract_int_type = Type::make_abstract_integer_type();
  this->globals_->add_constant(Typed_identifier("iota", abstract_int_type,
						loc),
			       NULL,
			       Expression::make_iota(),
			       0);

  Function_type* new_type = Type::make_function_type(NULL, NULL, NULL, loc);
  new_type->set_is_varargs();
  new_type->set_is_builtin();
  this->globals_->add_function_declaration("new", NULL, new_type, loc);

  Function_type* make_type = Type::make_function_type(NULL, NULL, NULL, loc);
  make_type->set_is_varargs();
  make_type->set_is_builtin();
  this->globals_->add_function_declaration("make", NULL, make_type, loc);
  ///...
}

make语义

编译器内部对于该调用的类型和意义相当于都做了处理。

///@file: gcc\go\gofrontend\expressions.cc
// Lower a make expression.

Expression*
Builtin_call_expression::lower_make(Statement_inserter* inserter)
{
  Location loc = this->location();

  const Expression_list* args = this->args();
  if (args == NULL || args->size() < 1)
    {
      this->report_error(_("not enough arguments"));
      return Expression::make_error(this->location());
    }

  Expression_list::const_iterator parg = args->begin();

  Expression* first_arg = *parg;
  if (!first_arg->is_type_expression())
    {
      go_error_at(first_arg->location(), "expected type");
      this->set_is_error();
      return Expression::make_error(this->location());
    }
  Type* type = first_arg->type();

  bool is_slice = false;
  bool is_map = false;
  bool is_chan = false;
  if (type->is_slice_type())
    is_slice = true;
  else if (type->map_type() != NULL)
    is_map = true;
  else if (type->channel_type() != NULL)
    is_chan = true;
  else
    {
      this->report_error(_("invalid type for make function"));
      return Expression::make_error(this->location());
    }

  Type_context int_context(Type::lookup_integer_type("int"), false);

  ++parg;
  Expression* len_arg;
  bool len_small = false;
  if (parg == args->end())
    {
      if (is_slice)
	{
	  this->report_error(_("length required when allocating a slice"));
	  return Expression::make_error(this->location());
	}
      len_arg = Expression::make_integer_ul(0, NULL, loc);
    }
  else
    {
      len_arg = *parg;
      len_arg->determine_type(&int_context);
      if (!this->check_int_value(len_arg, true, &len_small))
	return Expression::make_error(this->location());
      ++parg;
    }

  Expression* cap_arg = NULL;
  bool cap_small = false;
  if (is_slice && parg != args->end())
    {
      cap_arg = *parg;
      cap_arg->determine_type(&int_context);
      if (!this->check_int_value(cap_arg, false, &cap_small))
	return Expression::make_error(this->location());

      Numeric_constant nclen;
      Numeric_constant nccap;
      unsigned long vlen;
      unsigned long vcap;
      if (len_arg->numeric_constant_value(&nclen)
	  && cap_arg->numeric_constant_value(&nccap)
	  && nclen.to_unsigned_long(&vlen) == Numeric_constant::NC_UL_VALID
	  && nccap.to_unsigned_long(&vcap) == Numeric_constant::NC_UL_VALID
	  && vlen > vcap)
	{
	  this->report_error(_("len larger than cap"));
	  return Expression::make_error(this->location());
	}

      ++parg;
    }

  if (parg != args->end())
    {
      this->report_error(_("too many arguments to make"));
      return Expression::make_error(this->location());
    }

  Location type_loc = first_arg->location();

  Expression* call;
  if (is_slice)
    {
      Type* et = type->array_type()->element_type();
      Expression* type_arg = Expression::make_type_descriptor(et, type_loc);
      if (cap_arg == NULL)
	{
	  Temporary_statement* temp = Statement::make_temporary(NULL,
								len_arg,
								loc);
	  inserter->insert(temp);
	  len_arg = Expression::make_temporary_reference(temp, loc);
	  cap_arg = Expression::make_temporary_reference(temp, loc);
	  cap_small = len_small;
	}

      Runtime::Function code = Runtime::MAKESLICE;
      if (!len_small || !cap_small)
	code = Runtime::MAKESLICE64;
      call = Runtime::make_call(code, loc, 3, type_arg, len_arg, cap_arg);
    }
  else if (is_map)
    {
      Expression* type_arg = Expression::make_type_descriptor(type, type_loc);
      call = Runtime::make_call(Runtime::MAKEMAP, loc, 4, type_arg, len_arg,
				Expression::make_nil(loc),
				Expression::make_nil(loc));
    }
  else if (is_chan)
    {
      Expression* type_arg = Expression::make_type_descriptor(type, type_loc);
      call = Runtime::make_call(Runtime::MAKECHAN, loc, 2, type_arg, len_arg);
    }
  else
    go_unreachable();

  return Expression::make_unsafe_cast(type, call, loc);
}

覆盖(shadow)

回到最开始的问题:make不是关键字的原因在于它作为一个标识符是可以被覆盖的。

在添加的过程中,和C++编译器实现类似,也是包含了分层的Bindings中(函数是Bindings类的一个接口)。

///@file: gcc\go\gofrontend\gogo.cc
// Add a generic Named_object to a Contour.

Named_object*
Bindings::add_named_object_to_contour(Contour* contour,
				      Named_object* named_object)
{
  go_assert(named_object == named_object->resolve());
  const std::string& name(named_object->name());
  go_assert(!Gogo::is_sink_name(name));

  std::pair<Contour::iterator, bool> ins =
    contour->insert(std::make_pair(name, named_object));
  if (!ins.second)
    {
      // The name was already there.
      if (named_object->package() != NULL
	  && ins.first->second->package() == named_object->package()
	  && (ins.first->second->classification()
	      == named_object->classification()))
	{
	  // This is a second import of the same object.
	  return ins.first->second;
	}
      ins.first->second = this->new_definition(ins.first->second,
					       named_object);
      return ins.first->second;
    }
  else
    {
      // Don't push declarations on the list.  We push them on when
      // and if we find the definitions.  That way we genericize the
      // functions in order.
      if (!named_object->is_type_declaration()
	  && !named_object->is_function_declaration()
	  && !named_object->is_unknown())
	this->named_objects_.push_back(named_object);
      return named_object;
    }
}

疑问

基于类型的运行时分配,除了make/new这种操作还可以有其他的实现方式吗?如果没有,那么make/new是不是从逻辑上说就是一个语言不可缺少的一部分:也就是从逻辑上说应该算作这个语言的关键字?