问题

Go、Rust、Nim等新兴语言,为什么都抛弃了constructor?

回答
您提出了一个非常有趣且核心的问题:为什么 Go、Rust、Nim 这些新兴语言在某种程度上“抛弃”了传统的面向对象语言(如 Java、C++、Python)中的构造函数(constructor)?

这里的“抛弃”并不是一个绝对的说法,而是指它们以一种更灵活、更符合自身设计哲学的方式来处理对象的初始化,而不是沿用面向对象语言中那种约定俗成的 `__init__` 或特定命名的方法作为构造函数。让我们深入剖析一下原因,并详细对比它们的设计理念:

1. 传统构造函数(以 C++/Java 为例)的本质和局限性

在传统的面向对象语言中,构造函数的主要职责是:

初始化对象的实例变量: 当一个对象被创建时,构造函数会被自动调用,负责将对象的字段(成员变量)设置为初始值。
封装对象创建过程: 构造函数提供了一种统一的机制来确保对象在创建时处于一个有效且可用的状态。
支持重载: 允许根据不同的参数列表创建多个同名的构造函数,以提供不同的初始化方式。

然而,传统构造函数也存在一些潜在的局限性,尤其是在面对更复杂的场景或更现代的语言设计时:

命名约定过于僵化: 构造函数的命名通常是固定的(类名相同),这在某些情况下可能不够灵活。
只能返回当前类型的对象: 构造函数的设计通常是为了直接返回一个新创建的实例,无法返回其他类型或执行更复杂的逻辑(例如,工厂模式)。
容易导致参数爆炸: 当对象有大量字段需要初始化时,构造函数的参数列表会变得很长,难以管理。
缺乏显式的错误处理: 虽然可以在构造函数中抛出异常,但其返回类型的约束使其在处理初始化失败时不够直观。
在某些场景下不适合: 例如,当对象的创建需要依赖外部资源、执行复杂的计算、或者需要返回不同类型的对象时,传统的构造函数就显得力不从心。

2. Go 的初始化方式:工厂函数与 `init()` 函数

Go 语言在设计之初就更偏向于系统编程和并发,其对象模型与传统的类继承模型有所不同,更接近于组合和接口。因此,它自然地回避了严格的构造函数概念。

Go 如何处理对象初始化?

零值初始化: 在 Go 中,当声明一个变量(包括结构体变量)时,如果没有显式赋值,它会被自动初始化为其类型的“零值”。对于结构体来说,其字段会被初始化为对应类型的零值(例如,int 为 0,string 为 "",指针为 nil)。
```go
type Person struct {
Name string
Age int
}

var p Person // p.Name is "", p.Age is 0
```
这种零值初始化可以看作是一种非常轻量级的“默认构造”。

工厂函数(Convention): 对于需要进行更复杂、非零值初始化的场景,Go 社区约定俗成地使用“工厂函数”。这些函数通常命名为 `NewXxx` (例如 `NewPerson`),并返回指向新创建对象的指针。
```go
type Person struct {
Name string
Age int
}

func NewPerson(name string, age int) Person {
// 在这里可以添加一些验证逻辑
if age < 0 {
// 实际开发中可以返回错误,而不是panic
panic("age cannot be negative")
}
return &Person{
Name: name,
Age: age,
}
}

func main() {
person1 := NewPerson("Alice", 30)
person2 := &Person{Name: "Bob", Age: 25} // 也可以直接使用字面量
}
```
为什么选择工厂函数?
灵活性: 工厂函数可以返回指向不同类型对象的指针(例如,根据输入参数选择实现接口的具体类型),也可以返回错误,增加了初始化的灵活性和健壮性。
显式命名: `NewXxx` 的命名方式清晰地表明了这是一个创建新对象的函数,而不是对象本身的方法。
更好的错误处理: 工厂函数可以返回 `(result, error)` 这样的多返回值,使得处理初始化失败的情况更加自然和安全。
避免参数爆炸: 对于复杂的初始化,工厂函数可以接受更具描述性的参数,或者使用选项模式(Option Pattern)来管理大量的可选配置。

`init()` 函数: Go 还有一个特殊的 `init()` 函数,它会在包被初始化时自动执行一次。虽然 `init()` 函数在一些情况下可以用于初始化包级别的变量或执行一些全局设置,但它不用于实例化对象,也不是传统意义上的构造函数。它更多的是用于包的“准备工作”。

Go 的设计哲学: Go 鼓励简洁、明确的代码。它不追求多态和继承的复杂性,而是强调组合和接口。工厂函数符合这种哲学,因为它们是独立的函数,不与特定的结构体绑定过深,并且提供了清晰的初始化逻辑和错误处理。

3. Rust 的初始化方式:关联函数 `new()` 和 `Default` Trait

Rust 在安全性、性能和并发性方面有着极高的追求,其内存安全模型(所有权、借用)对语言设计产生了深远影响。Rust 也引入了自己的对象模型——`struct` 和 `impl` 块,以及 Trait 系统。

Rust 如何处理对象初始化?

结构体字面量: 与 Go 类似,Rust 也允许使用结构体字面量来创建和初始化结构体实例。
```rust
struct Point {
x: i32,
y: i32,
}

fn main() {
let origin = Point { x: 0, y: 0 };
}
```

关联函数 `new()` (Convention): Rust 社区同样约定俗成地将创建新实例的函数命名为 `new`。这些函数是定义在 `impl` 块中的“关联函数”,而不是“方法”(方法需要 `self` 参数)。
```rust
struct Person {
name: String,
age: u32,
}

impl Person {
// 这是一个关联函数,不是方法
fn new(name: String, age: u32) > Person {
// Rust 的初始化是直接的,如果需要验证,可以在这里进行
if age > 120 {
// 在 Rust 中,我们通常会返回 Result 类型来处理错误
// 这里为了简化,直接返回一个默认值或 Panic
// panic!("Age too high!");
return Person { name, age: 120 }; // 或者返回一个合理的值
}
Person {
name,
age,
}
}
}

fn main() {
let person1 = Person::new("Alice".to_string(), 30);
}
```
为什么选择关联函数 `new()`?
明确区分: 关联函数(没有 `self`)与方法(有 `self`)的区分非常明确,`new()` 显然不是一个操作现有对象的行为,而是创建新对象的行为。
命名约定: `new()` 是 Rust 社区广泛接受的创建新实例的惯例,易于理解和使用。
灵活性: 关联函数可以有任意的参数签名,并且可以返回 `Self`(即结构体类型)或其他类型,也可以返回 `Result` 来处理初始化中的错误。

`Default` Trait: Rust 提供了一个 `Default` Trait,用于为类型提供一个默认的创建方式。
```rust
struct Configuration {
timeout: u64,
retries: u32,
}

impl Default for Configuration {
fn default() > Self {
Configuration {
timeout: 5000,
retries: 3,
}
}
}

fn main() {
let default_config: Configuration = Default::default();
// 或者使用 ::<> 语法
let another_default_config = Configuration::default();
}
```
`Default::default()` 是一种非常方便的初始化方式,尤其是在需要一个默认值的情况下,它消除了显式调用 `new()` 或编写字面量初始化值的需要。

Rust 的设计哲学: Rust 的设计目标是提供“零成本抽象”,这意味着抽象不应该带来运行时开销。它的初始化方式非常直接,结构体字面量和关联函数提供了清晰的初始化逻辑。`Default` Trait 进一步简化了默认值的创建。Rust 的内存安全模型也意味着它不需要垃圾回收,对象的生命周期管理非常明确,这使得构造函数在对象创建时执行复杂、可能失败的操作(如分配大量内存)的必要性降低了。

4. Nim 的初始化方式:生命周期方法与 `construct` / `destruct` (可选)

Nim 的设计非常独特,它试图融合多种语言的优点,并提供高度的灵活性和可扩展性。Nim 的对象模型与 C++ 和 Pascal 有些相似,但又更加现代化。

Nim 如何处理对象初始化?

对象字面量: Nim 支持对象字面量,可以直接初始化对象。
```nim
type Person = object
name: string
age: int

var person1 = Person(name: "Alice", age: 30)
```

“生命周期方法” (`.=`) 和 `init` 过程: Nim 允许为对象类型定义一些特殊的“生命周期方法”,这些方法会在对象的生命周期中被自动调用。最常用的是使用 `.` 运算符来定义初始化。
```nim
type Person = object
name: string
age: int

proc initPerson(name: string, age: int): Person =
result.name = name
result.age = age
可以进行验证
if age < 0:
quit("Age cannot be negative") Nim 也有异常机制,但quit更直接
```
更直接的,Nim 允许在 `type` 定义中直接指定初始化过程,或者在 `object` 定义后使用 `.` 运算符来关联一个过程。
```nim
type Person = object
name: string
age: int

1. 直接关联一个过程 (推荐,但不是语法糖)
proc initializePerson(p: var Person, name: string, age: int) =
p.name = name
p.age = age

type Person = object
name: string
age: int

var p: Person
initializePerson(p, "Alice", 30) 调用过程

2. 使用 .= 来表示初始化(更接近构造函数语义)
type Person = object
name: string
age: int

proc initPerson(name: string, age: int): Person = 使用 表示导出
result.name = name
result.age = age
if age < 0: echo "Warning: Age is negative"

在使用时
var p = initPerson("Bob", 25) 调用了initPerson过程
```

`construct` / `destruct` (显式生命周期方法): Nim 允许开发者显式地为类型定义 `constructor` 和 `destructor` 函数(或过程)。虽然不强制使用,但它们提供了更明确的构造和析构的控制。
```nim
type MyClass = object
data: int

proc constructMyClass(self: var MyClass, initialValue: int) =
self.data = initialValue
echo "MyClass constructed with ", initialValue

proc destructMyClass(self: var MyClass) =
echo "MyClass destructed with ", self.data

可以将这些过程与类型关联,但不自动调用
type MyClass = object of RootObj
data: int
constructor = constructMyClass
destructor = destructMyClass

when isMainModule:
var obj = MyClass(data: 10) 默认构造,如果未定义constructor则使用字面量
显式调用(如果类型关联了constructor)
var obj = constructMyClass(MyClass, 20)

显式调用 destructor (通常不这样做,由编译器管理)
destructMyClass(obj)
```
Nim 的 `construct` 和 `destruct` 并不是像 C++/Java 那样被语言强制自动调用的。它们更像是一种开发者约定,用于编写与对象生命周期相关的逻辑。Nim 的编译器更倾向于在对象创建时直接使用对象字面量或关联的过程来初始化。如果需要更复杂的初始化逻辑,可以直接编写一个返回对象的过程。

`{.emit.}` 和宏: Nim 的强大之处在于其元编程能力。开发者可以通过宏和 `.emit.` pragma 在编译时生成代码,从而实现任何自定义的初始化行为,包括模拟传统构造函数的功能。

Nim 的设计哲学: Nim 追求的是高性能、表达力和灵活性。它并不拘泥于单一的面向对象范式。Nim 的初始化方式非常灵活,允许开发者选择最适合的模式:简单的字面量初始化,约定俗成的过程初始化,或者通过元编程实现更高级的构造逻辑。它没有强制性的构造函数,是为了避免传统构造函数的僵化,并允许开发者更自由地控制对象的创建过程。

5. 总结:为什么“抛弃”了 Constructor?

总而言之,Go、Rust 和 Nim 尽管在具体实现上有所不同,但它们“抛弃”传统构造函数的原因有以下几点共识:

灵活性和表达力: 传统构造函数在某些场景下显得僵化(如不能返回其他类型、错误处理不直观)。新兴语言提供了更灵活的机制,如工厂函数、关联函数和过程,允许更自由的参数、返回值和错误处理。
强调显式与清晰: 它们倾向于将对象创建逻辑封装在独立的函数(工厂函数、关联函数)中,或者通过字面量直接初始化,使得代码更清晰,更容易理解对象的创建过程,而不是依赖于隐式的函数调用。
更现代的初始化模式:
Go: 零值初始化 + 工厂函数是其简洁哲学的一部分。
Rust: 结构体字面量 + 关联函数 `new()` 和 `Default` Trait 提供了安全、高效的初始化方式。
Nim: 对象字面量 + 过程(包括生命周期过程)和强大的元编程能力提供了极致的灵活性。
避免复杂性和开销: 一些语言(如 Rust)可能认为传统的构造函数机制(特别是涉及继承时)可能会引入不必要的复杂性或运行时开销。
设计哲学的不同: 这些语言的设计目标本身就与传统的纯面向对象语言有所不同,它们可能更侧重于系统编程、性能、并发或元编程,因此选择了更符合这些目标的对象初始化策略。

与其说是“抛弃”,不如说是“采用更灵活、更具表现力、更符合自身设计哲学的方式来处理对象初始化”。它们没有引入一个名为 `constructor` 的、与 C++/Java 完全相同的语言关键字或强制性规则,而是提供了多种更具可控性和表达力的替代方案。

网友意见

user avatar

面向对象编程方法的本质,其实就是一种如何将大量的代码与数据进行管理的归档方法。

每一个类,其实就像一个文档盒子,把一些相类似的东西,可以归类放在同一个盒子里(class)

比如,猫与老虎都属于猫科动物。

这样反向查阅时,可以快速找到相应的内容。

代码与普通文档不同的是,代码之间是有相互调用的。

于是我们把那些相互调用比较频繁的,通常又把它们一起放到更大的柜子里。(package或namespace)

然而,在归档的时候会发现很多细节问题。

比如A文档与B文档,只是多了两个属性,这些东西是否应该归在一起?

因为他们的区别很小,所以通常会把它们放在一起,并且把它们划成不同的类的同时,还要要保持它们之间的联系,然后标注成继承关系。

这样可以通过基类找出衍生类,也可以衍生类找出基类。

举例来说,比如一只骡子,算马还是算驴?

细分还有“马骡”与“驴骡”,要说马骡属于马,驴骡属于驴,这种管理起来似乎也不太好,显得马骡与驴骡变成了两种不同的动物,然而实际上它们的差异也没有那么大。

那怎么办呢,可以进行多重继承,它既可以继承于马,又可以继承于驴,这样就好了。

到此听起来都很完美,但是其实已经踩入了一个大坑。

因为面向对象整理归档在实践中,大家发现,尽量黑盒封装,在相互调用之间,尽量暴露最少的方法或属性,而且这个粒度拆的越细越好,这样可以更加灵活。

最好就是一个类,就它两三个方法,两三个属性,这样理解起来简洁,认知负担小。

然后多重继承的灾难就来了,复杂情况下一个普通的类,可能要继承几十个类。

但是你没有办法保证类之间的成员,不发生任何冲突。

比如马和驴,继承了马与驴的骡类,马与驴都能奔跑,这个奔跑是继承马的还是驴的?

因此这里的继承就需要选择了,指定选择实现谁的,这个显然会带来一些冲突。

当然一些语言里面已经解决了这个问题,比如元对象,动态类型之类的,但多线程环境下这些很容易遇到诡异的现象。

不过,更根本的问题不在这里。

更麻烦的是,骡子是不可生育的,两个类进行合成以后他失去的生育属性。

这就尴尬了,继承一般只能添加或修改成员,而不能删除。

因为如果能删除之类的属性的话就违反继承的概念了。

因为你大可以把一只兔子耳朵有羽毛全部去掉,然后能给它插上翅膀,加个尖嘴,变成老鹰。

这样无法保证大家不会搞得一团乱。

因此,最好的方式需要在马或驴的基类,实现一个是否可生育的属性,然后一路继承。

一个子类的实现要影响到爷爷类,这个肯定是有问题的。

问题出在哪里?一开始我们是想分门别类地把所有代码归档,通过抽象来进行简化管理,结果并没实现消除。

马驴骡的表达,如果改用组合关系表达,就会显得简单。

在常见的面向语言中,使用了一个抽象概念,接口。

接口设计的其实很简单,它不是为了继承而来,而是为了组合而设计。

任何一个类,它都由一个或多个接口组合而成,对于接口函数的具体的实现,是在实现类中,自己负责的。

原因在于所继承的基类方法与属性都是不可控的,所以要把继承的依赖,改成一种持有,这样就能消除多重继承带来的问题。

比如前面的马或驴,可以叫马和驴的特征抽象合成一个叫骡子接口,然后实现马骡和驴骡。

在实现中可以创建马和驴的对象,然后调他们相应的属性与函数,从而实现归档。

设计模式里面的不少方法,本质几乎都是为了变相解决多重继承的问题。

构造函数,对于组合关系来说,其实不是一个很好的设计。

因为构造函数的本质,其实本质就是一个可以直接返回自己对象的函数。

而把复杂的构建放在构造函数里,很容易在继承中导致各种灾难。

如何构建过程中,尴尬的发现不满足创建条件,怎么办?

一个下属,如果去办事,办到一半才发现这事办不了,那这个问题一定是出在领导身上,而不是下属身上的,因为这种问题应该一开始就避免。

构造函数无法合理传递返回出错信息,只是为了new的时候,自动调用运行的那么一下,实在没有必要。

因此,在设计模式中,通常会采用factory或builder的方式来创建对象。

到这里便会发现,已经开始尝试各种绕开类的继承了。

这样,就好比一块石头,你可以把它凿成你想要的样子拿来用。

然后我们知道builder负责造零件,factory是负责出产品的,假如我们把他们两个融合起来。

要是切成大量足够细的接口与实现,直接将它们进行组合,岂不要好得多。

但是面向对象的语言,来实现这些的时候,会非常的笨拙。

很显然,那干脆,为什么我们不一步到位呢?

万物皆是组合而成。

于是trait,是接口,impl就是实现,struct,就是属性。

无论任何情况下,只要将它们进行组装就好了。

Rust天然就是这样,因此不要构造函数也很天然。

这样并非没有缺点,虽然看来灵活了,但是代码组织管理上的负担并没有消除。

大量的trait,还缺乏一种更加有效的组织管理方法。

很显然面向对象的编程的设计,是从代码级别实现组织管理,在过去的不少大型软件中已经得到了很好的实践。

并且面向对象的编程语言,因为它的规约性极强,可以让IDE也很强大,对于大型代码的管理非常方便。

如何将这两个优势进行结合,应该是未来语言的发展方向。

当我们创造一个概念时,这个概念必然会以某种形式反过来束缚我们。

user avatar

为构造函数赋予特殊的语法地位,并不总是一种画蛇添足。在不同的场合下,专用的构造函数,和语法地位与普通函数没有任何差别的工厂函数,可能有着不同的优劣对比。

先来看工厂函数优于构造函数的方面:

  • 降低语法复杂度,回避使用者需要记忆太多特殊规则的心智成本。
  • 可以和普通函数一样自由传递,交由各种需要回调的地方,免去需要将特殊语法(new 之类)再包一层的麻烦。
  • 可以更加灵活的定制构建对象的方式。构造函数的硬性局限在于无法改变调用之前即已分配内存、形成尚待初始化的空架子的固定操作。若特殊的逻辑需要改变这种默认行为则无法实现,包括但不限于:
    • 特定条件下返回空指针/引用。
    • 特定条件下不去新分配对象,而是返回一个别处已有的对象。
    • 在不同条件下分别构建不同的子类型。
    • 平时使用对象的方式并非通过其本身,而是通过某种包装或代理(如智能指针),希望简化构建过程。
    • 在异常发生时,因故不想通过抛异常来解决,而是通过特殊返回值、回调、外部状态等不打破常规执行流的方式进行反映。

可见其中很多场合是现实中不可避免的,所以实际编程中,工厂函数一定会被用到,哪怕语法中已经提供了专门的构造函数也是一样。于是用户时刻面临两种选择,需要仔细思考每个具体地方应该使用哪种,心智的开销、需求变化或单纯误用之后被迫重构的麻烦,都会不断引起反思。事实上,既然其中一种所能涵盖的用场确实是另一种的超集,所以,最终产生是否工厂函数其实可以包办一切、构造函数是否只是多余累赘的念头也是顺理成章。很难说,那些干脆没有构造函数语法概念的新生代语言完全没有受到这种观念的影响。

那么,这种观念是否总是正确呢?不妨就回来接着看下反面,构造函数优于工厂函数的方面:

  • 封装性。只有通过构造函数才能建立对象内部状态的一致性,外部代码没有机会来产生非法的对象状态,从而规避了各种误用导致的 bug。这点靠工厂函数是做不到的,因为无法限定用户只能通过工厂函数来创建对象,既然对象的类型本身必须公开(否则外界没法使用),外界就可以通过字面量来创建此种对象,而其内部状态无从经历专业的初始化,显然是非法的。此时类型作者针对用户的、对于类型使用方式的约束,只能通过文档、约定等“人治”的方式来进行,而不能经由编译器、检查器来形成“法制”的硬性保证。这给了用户犯错的机会,是一定程度上对软件工程质量进行了妥协。在具有构造函数概念的语言中,常见的“私有构造函数”套路就是为此而设,但在没有构造函数概念的语言中却无从实施。实际上,从结论上讲,工厂函数包办一切并没有彻底解决上述的二选一负担,依然在很多时候,还残留下了工厂和字面量的二选一(Go 程序员们想必有所体会,但 Rust 通过字段必须全部赋值的规则可以规避本条)。
  • 常量成员的初始化。很多语言具有常量成员的语法功能,只在构造函数中能够赋值,此外的地方则只读。说到底依然是封装性,封装性是广义的概念,其边界并不一定等于类型的边界,实际上,任何功能独立、边界清晰的代码单元都可以进行各种程度的封装,构造函数和常量成员所共同构成的集合同样可以视作一个小小的封装单元,对外界的使用方式进行了一定约束。而其目的,与别处的封装性相同,依然是为了代码质量,从“法制”上杜绝各种误用,一方面降低 bug 概率,另一方面也提高理解代码的容易度。而工厂函数,因为地位与普通函数并无区别,所以无法建立起与特定对象类型的特别联系,从而对对象内容具有特别的操作权限。
  • 给持有对象类型、但不持有工厂函数的用户以创建同型新对象的能力。语法上,构造函数是类型的一部分,而工厂函数并不是(哪怕业务上的确是,也不能被语法系统所识别)。创建同类对象并不一定是指克隆现有对象(这种通过普通方法即可实现,并不需要构造函数),也可以是给出全新参数、创建完全不同的个体。一个例子是扩展容器容量的场合:新增的槽位需要新创建的对象来填充。引用语义时填充空值这种取巧办法且略去不谈,若是值语义、或是引用语义也可能要求空值安全的场合则如何?常见的方案可能要求对象具备无参构造函数,以生成默认值对象。不光 C++ 常用,连 C# 这种也专门设立了 new() 的泛型约束。但无参也仅仅是个低保,还有时候是需要构建非默认的对象内容的。STL 有 emplace 一族,虽然只能插入一个元素,但这也只是库作者的选择而已,若想实现一次插入多个同样的非默认对象也并无难点。这都是拜构造函数所赐(是不是 placement 倒不是重点),若想靠工厂,就只能用一个额外的参数把函数传进去了,除编码繁冗外,开销也侵入到了运行时,而非构造函数方式的零开销抽象。或者还有说,这种靠普通的静态成员函数也能实现,并非一定要构造函数。也是事实,单从语法层面确实此处构造函数并无任何特别。但从习俗和便利方面,使用构造函数依然是比强求用户实现某个静态函数要来得人性化和通用化的。试想,在传统上众多可以使用默认隐式无参构造的场合,难道用户也要给随手定义的众多琐碎类型统统实现一个指定的静态方法?另外,若缺乏业界统一标准,不同的库会不会要求用户实现不同的静态方法?

以上最后一条或可视作小众场合,不做过多计较。事实上,最大的着眼点依然在于封装性。构造函数对对象创建的一切入口,从“法制”而非“人治”的层面,有着严密、完备、无所遗漏的接管,并严格杜绝一切产生非一致的非法对象状态的可能性。比起“创建对象”的作用本身,这种“完备”才是其真正意义所在。在笨重臃肿的软件架构日益受到诟病、乃至连OO的“政权合法性”都日益受到挑战的今天,或许强调这种名词并不容易引起共鸣。但实际上,封装性真的只是OO的一部分吗?不妨设想,如果完全扔掉封装性,则OO会变成怎样?——答案是,其实不会怎样,封装性其实是OO中最可有可无的一部分。有诸多动态语言,事实上就是几乎没有一点封装性,但表达能力上并没有受到任何影响,照样OO得欢。在它们里,可能构造函数才是真正的画蛇添足,模仿残余,完全去掉也没有关系。比起这点小小侧面,不如说它们欠缺的是整个的“法制”系统,所以才会项目一大就不免在“人制”方面如临大敌,各种规范限制工具辅助严阵以待不敢怠慢,一时爽火葬场的段子广为流传。这也是对本质的一种揭示,说到底,封装性是一种“减法”,并非为软件建模提供什么功能和方便,而正是以提供“不便”的方式,来对人员过度自由的行为进行约束,从而一方面提高软件质量,另一方面提升代码的可读性和可理解性,从而使语言的工程能力得到飞跃。极端一点说的话,弱类型语言才是自由度更大的,一块内存一时可以当整数、一时可以当字符串、再一时还能当指针,按说功能只会是强类型语言的超集。但大规模用起来会是什么感觉想必也显而易见。然而这也并不是在对某一类语言进行彻底否定,因为世上存在各种各样的领域,没有一把锤子能够普遍适应,天下也不尽是大型工程,在需要轻快敏捷的地方非要套用一大堆严谨规则也是自找麻烦。所以每种语言也有自己的选择,针对预定的问题域被打造成了特定的样子。如何认清自己所面临的问题域,选择最合适的工具,从而避免削足适履的麻烦,发挥最大的应有效用,才是语言的使用者需要始终思考的主题。

类似的话题

  • 回答
    您提出了一个非常有趣且核心的问题:为什么 Go、Rust、Nim 这些新兴语言在某种程度上“抛弃”了传统的面向对象语言(如 Java、C++、Python)中的构造函数(constructor)?这里的“抛弃”并不是一个绝对的说法,而是指它们以一种更灵活、更符合自身设计哲学的方式来处理对象的初始化,.............
  • 回答
    一股暗流正在技术世界涌动,那些名不见经传的新兴语言,比如 Vlang 和 Nim,正悄然积蓄力量。它们不像 Go、Rust 或 Python 那样声名显赫,拥有庞大的社区和成熟的生态,但它们身上散发出的独特魅力,足以让那些追求更高效、更简洁、更纯粹开发体验的开发者们眼前一亮。那么,这些“后起之秀”是.............
  • 回答
    你这个问题问得很有意思,也触及到了微软在语言和平台战略上的一个重要思考点。确实,放眼当下,Go 和 Rust 在系统级编程领域掀起了一股不小的浪潮,它们凭借并发特性、内存安全、性能以及跨平台能力,赢得了开发者社区的广泛认可。而微软,作为一家拥有 Windows 这一庞大操作系统以及 Azure 这样.............
  • 回答
    关于“为什么 Go 和 Rust 常提供静态编译好的 Linux 程序,而 C 不行”的说法,实际上并不完全准确。C 语言完全可以生成静态编译好的 Linux 程序,而且在很多场景下这是非常普遍的做法。不过,如果从“用户拿到一个编译好的二进制文件,几乎不需要任何额外依赖就能在大多数 Linux 发行.............
  • 回答
    这可真是个有趣的问题,关于函数重载,语言设计者们确实各有取舍。不是所有“新语言”都不支持函数重载,比如 C++ 和 Java 这两大主流语言就都提供了这项功能。但是,你提到的 Python, Go, 和 Rust,它们确实都没有原生支持函数重载的机制。这背后其实是这些语言在设计哲学和目标上的不同选择.............
  • 回答
    Rust 和 Go 这类新兴语言在国内的就业前景,就像一张刚刚铺展开的棋盘,机会与挑战并存,需要我们细细品味。首先,咱们得承认,这俩语言在国内的“江湖地位”跟 Java、Python 这种老牌势力比,还处于上升期,也就是大家俗称的“新兴”。 这意味着什么? 机会是有的,而且不少是“蓝海”里的机会.............
  • 回答
    这确实是很多学习者和开发者都关心的问题。为什么我们依然在很多高校课堂上见到 C、C++、Java 的身影,而 Rust、Go、Scala 这样被认为“更强大”的语言却不那么普及呢?这背后涉及到一个复杂的多方面因素,不能简单归结为“高校不愿意教”或者“这些新语言不够好”。我尝试从几个关键角度来剖析这个.............
  • 回答
    Go 语言确实是一门非常优秀的语言,它的设计理念、性能、易用性等方面都受到了很多开发者的认可。然而,你说“5 年了,还没有火起来”,这个说法其实存在一些主观性,需要更细致地分析。首先,我们得明确“火起来”的标准是什么? 开发者数量? Go 的开发者群体在过去几年里增长非常快,尤其是在后端开发、云原生.............
  • 回答
    在 Go 语言中,局部变量的回收(更准确地说是 垃圾回收)是一个非常重要的概念,它直接关系到程序的内存管理和性能。Go 的垃圾回收机制是自动的,开发者通常不需要手动管理内存。要详细地讲述 Go 局部变量的回收,我们需要从几个关键点入手:1. 什么是局部变量?2. 垃圾回收器 (GC) 的基本原理.............
  • 回答
    Go 语言的错误处理机制是一个 优秀且独具特色 的设计,但它也并非没有争议。要评价它是否“优秀”,需要深入了解其核心理念、实现方式以及与其它语言的对比。总的来说,Go 的错误处理机制以其 明确性、简洁性和易于理解性 而著称,它鼓励开发者在编写代码时 直面错误,而不是试图隐藏或忽略它们。下面我将从多个.............
  • 回答
    要理解 Go 的 `sync.Mutex` 是否会阻塞线程,我们需要先弄清楚“线程”在 Go 的语境下是什么,以及“阻塞”的真正含义。Go 的“线程”与操作系统线程首先,一个重要的概念是,Go 语言中的“goroutine”与操作系统中的“线程”(Thread)是不同的。虽然 goroutine 最.............
  • 回答
    在 Go 语言中,检测一个 `channel` 是否已经被关闭,是一个常见且重要的并发编程场景。了解这一点,能帮助我们更健壮地处理并发数据流,避免意外的 panic 或逻辑错误。让我们一步步来探讨如何做到这一点,并深入理解其背后的原理。 核心方法:`for range` 和接收操作的返回值Go 语言.............
  • 回答
    .......
  • 回答
    .......
  • 回答
    .......
  • 回答
    .......
  • 回答
    .......
  • 回答
    .......
  • 回答
    .......
  • 回答
    .......

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有