服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - 编程技术 - Zig是码农们期待已久的C语言的替代品吗?

Zig是码农们期待已久的C语言的替代品吗?

2020-11-17 22:21今日头条闻数起舞 编程技术

从很多方面来说,我整个编程生涯都像是在等待C的替代产品的漫长等待。20年前,尽管我用C ++。 随着时间的流逝,我了解到C ++是一个复杂的怪物,无论我读了多少本书,都永远无法驯服。

与以前的C竞争者(例如C ++,D,Java,C#,Go,Rust和Swift)的比较

从很多方面来说,我整个编程生涯都像是在等待C的替代产品的漫长等待。20年前,尽管我用C ++。 随着时间的流逝,我了解到C ++是一个复杂的怪物,无论我读了多少本书,都永远无法驯服。

我认为Yossi Kreinin和他的C ++常见问题解答(https://yosefk.com/c++fqa/)在总结我对C ++讨厌的各个方面都做得很好。

Zig是码农们期待已久的C语言的替代品吗?

因此,在成为一名专业的C ++程序员时,我总是着眼于其他选择。 第一个有希望的替代品是D。D最初看起来很有前途,但经过仔细检查,我认为D实际上只是一个根本上不好的主意的清理版本。 C ++的主要问题之一是它的接收器语言设计方法。

当用C和Lua实现一个简单的游戏引擎时,我意识到与C ++相比,同时保留这两种语言的思路实际上更少了。 它给我带来了对C的重新热爱。尽管有其所有局限性,C是一种相当简单的语言,可以提供很多控制。

Java和C#在许多方面只是尝试重新实现C ++。他们可能使事情变得更简单,但最终却陷入了虚拟机和90年代面向对象的编程炒作中。不是说Java或C#不好,其中很多可能与那些支持臃肿的IDE和过度设计的语言的相关社区有关。

Zig是码农们期待已久的C语言的替代品吗?

C简单性的回归

从Google转到Go是对多余的C ++,D,Java和C#的欢送。Go将我们带回到了起点,回到了C。Go重新设想了如果不冒险走C ++道路,C可能会是什么。我们得到的是一种简单的语言,它修复了我在C语言中经常遇到的许多问题。

但是故事还没有结束。 紧随Go之后,我们得到了Rust。 最初,我认为Rust实际上是D应该一直以来的目标。 对C ++应该是什么的真正的重新思考。 Rust保持了低级控制,高级抽象机制和手动内存管理,但增加了无与伦比的类型安全性。 一切看起来都太好了,难以置信。

坦率地说,我认为是。我记得能够在两天内用Go编写一些不错的程序。朱莉娅,我目前的最爱也有些相似。另一方面,学习Rust就像学习Haskell。在做任何有用的事情之前,只需了解许多概念和理论即可。

如果C ++教会了我任何东西,那就是要重视简单性,而Rust不会这样做。

后来,我从互联网上许多Rust用户的评论中学到了,Rust重复了C ++的主要缺点之一。它的编译时间确实很慢。我认为没有什么比等待C ++编译破坏我编程的乐趣了。听起来好像Rust更糟了。那是一个破坏交易的因素。

Zig是码农们期待已久的C语言的替代品吗?

Swift-我想爱的语言

我20年来一直是苹果的忠实粉丝。 我很喜欢Cocoa GUI库,并在没有iPhone之前就对Objective-C进行了编程,突然,每个人和他们的宠物都在用Objective-C进行编程。

是的,Objective-C有点笨拙,但它的简单性具有一定的美感。 与C ++不同,它是对C语言的相当简单的补充。 根据经验,您实际上可以真正快速地教初级开发人员Objective-C。

因此,Swift的发布让我觉得我已经达到了编程的必杀技。最终,一种非常现代的语言与Objective-C很好地集成在一起,因此我们仍然可以使用很棒的Apple库,例如Cocoa。

Swift从Rust那里借来了很多想法,从很多方面来说,我认为我们终于为普通人获得了Rust。可以在相当短的时间内学会Swift。

但是我在Swift方面的经历好坏参半。 即使在今天,我也仍然无法正确表达语言的问题,因为它似乎可以解决很多问题。

我将iPhone应用程序从Objective-C移植到了Swift。 我最好的经历之一是Swift仅仅由于严格的类型系统就发现了一大堆错误,这些系统捕获了在Objective-C中不可见的问题,而这在编译时至少是关于类型的,这是众所周知的。

与C ++,C#和Java相比,我会说Swift是更好的语言。 Swift几乎解决了我所有有关C ++的特定问题。 但是我每次使用Go时都意识到,编写Go程序比Swift有趣得多。 但是Go的错误处理有点糟糕,它重复了具有空指针的百万美元错误。 Swift避免了这两个问题。

上次与Julia在一起很长时间后,我回到Swift时,看到Swift的一些问题变得更加清晰:

Swift语法不适合函数式编程

从Objective-C继承的Smalltalk启发式语法在面向对象的编程中很好地工作,但是对于函数式编程却非常糟糕。将函数用作一等公民时,您无需费心确保参数名称正确。

面向对象的编程和函数式编程之间的停战。Swift试图为两个不同的主人服务,并为此遭受痛苦。在进行非常实用的样式编程时,您希望您的函数主要是自由函数。这些更易于传递并在函数设置中使用。

但是Swift最终主要是面向OOP人群,将函数放在方法中。 一旦完成了许多函数编程,这就会变得很麻烦。

Zig适合编程领域的何处?

因此Swift从来没有真正成为我最终的通用编程语言。如果我想以更高的抽象级别进行编程,获得高性能并完成工作,我将选择Julia。

但这仍然为C之类的替代品留下了未填补的空间。朱莉娅(Julia)不能真正取代C。它吞噬了内存,无法产生小的二进制文件,不适合使其他语言可以使用的库。您不想使用它来创建OS内核或进行微控制器编程。

Go和Rust都真的接近于替换C。Go摆脱了使用C的简单性和使用感。但是它使用垃圾回收并不能完全替代C。与Java相比,在Go中对内存使用的更多控制仍然是毫无价值的,因为您可以获得指针,并且实际上可以创建自己的辅助分配器。

Rust降低了手动的内存分配,但是未能复制C的简单性和感觉。也许这两种语言之间是否有什么可以填补的空间?

确实有。 我认为这就是Zig。 Zig比Go更复杂,但比Rust更易于学习和使用。

但是,这样的Zig总结并不能使语言公正。Zig为表带来了很多新想法,这很有道理,并且使Zig编码的体验非常独特。但是在深入探讨之前,让我们先看一下基础知识。

正确掌握基础知识

如果我们要学习另一种类似C的语言,我们将无法重复C ++最糟糕的情况,例如糟糕的编译时间。 Zig如何解决这些问题?

我遇到了V编程语言的创建者Alexander Medvednikov进行的测试。这是编译具有400 K函数的文件的测试:

  • C 5.2秒 gcc测试
  • C ++ 1分25秒 g ++ test.cpp
  • Zig 10.1秒 Zig build-exe test.zig
  • Nim 45秒 nim c test.nim
  • Rust 30分钟 rustc test.rs 后Rust停止
  • Swift 在30分钟的swiftc测试后停止
  • D 6分钟后 segfault dmd test.d
  • V 0.6秒 v test v

Rust,Swift和D都失败了。Medvednikov用更少的行数对这些语言进行了进一步的测试,Rust再次表现出了最差的预期。

正如您在列表中所看到的,Zig是最杰出的演员。 尽管很难不注意到V语言会在不到一秒钟的时间内完成所有操作。 这使我想起更详细地探索V。 快速扫描表明它可以手动分配内存,泛型和可选(必须明确允许使用空指针)。

Zig内存分配

如果不进行手动内存管理,您将无法使用C语言。进行C风格编程的人都希望这样做。如果我不需要,那么我可以为Julia编程。

Zig没有提供Rust所提供的那种最高的安全性,但是如果不这样做,它所获得的是一个对于初学者来说更容易掌握和使用的模型。

需要在Zig中分配内存的任何内容都将分配器作为参数。 因此Zig非常明确地说明了何时需要内存管理。

这是我编写的一个简单函数,它使用32位无符号整数n并将其拆分为十进制数字:

fn decimals(alloc: *Allocator, n: u32) !Array(u32) { 

var x = n

var digits = Array(u32).init(alloc); 

errdefer digits.deinit(); 

while (x >= 10) { 

try digits.append(x % 10); 

xx = x / 10; 

try digits.append(x); 

return digits; 

请注意,必须使用分配器分配用于保留各个十进制数字的数组数字,该分配器是十进制函数的参数。

这就是Zig真正的光芒所在。 确保您不会忘记分配内存在C语言中很难。而且很容易以错误的位置结束内存。 Zig从Go复制了延迟概念。 但是除了推迟它还有errdefer。 如果您不了解Go,那么从本质上讲,延迟是将行或代码块的执行推迟到函数退出之前的一种方法。

为什么这么好? 因为它使您可以确保某些代码得以运行,而不管退出该函数之前使用了什么复杂的if-else语句。

errdefer digits.deinit(); 

上面的行与正常的Go延迟有所不同,因为只有在返回错误代码的情况下,它才会执行。因此,如果一切正常,那么它将永远不会运行。

在呼叫站点,我们将使用常规延迟来确保我们不会忘记释放分配给数字的内存。

const digits = try decimals(allocator, 4123); 

defer digits.deinit(); 

for (digits.items) |digit| { 

try print("{},", .{digit}); 

从我在Zig玩游戏方面的有限经验,我会说这是一个很好的系统。分配器和defer的结合使用使您非常清楚要分配和释放内存的位置,同时可以轻松正确地进行分配。

C兼容

许多类C语言的问题是它们无法与C配合使用。这意味着从该语言调用C函数应该很容易,而从C对该语言调用函数应该很容易。

此外,您编写程序的一般方式应该与C完全兼容,因此您不必创建较大的C抽象级别。例如。C ++对C语言不是很友好,因为没有大量包装就无法在C中使用典型的C ++库。

但是Zig非常C,因为它没有暴露C不会得到的奇怪的东西。 结构中没有vtable(C ++中的虚拟函数表)。 没有C知道如何调用的构造函数或析构函数。 也没有任何例外,C也会在捕获方面遇到困难。

从Zig使用C很简单。 实际上,Zig的创建者会声称Zig比C本身更擅长使用C库。

const c = @cImport({ 

@cDefine("_NO_CRT_STDIO_INLINE", "1"); 

@cInclude("stdio.h"); 

}); 

pub fn main() void { 

_ = c.printf("hello\n"); 

如您所见,Zig解析C头文件并包含来自C的类型和函数没有问题。实际上Zig是完全成熟的C编译器。您可以根据需要使用Zig编译C程序。

同样,将Zig函数暴露给C也很容易。这是一个Zig函数,采用32位整数并返回32位整数。

export fn add(a: i32, b: i32) i32 { 

return a + b; 

通过将export放在它的前面,可以使我们与程序链接的C代码可以访问它。实际上,我们的主要功能是在C代码部分中定义的,并且使用了Zig中定义的功能。

#include <stdint.h>int32_t add(int32_t a, int32_t b); 

int main(int argc, char **argv) { 

assert(add(42, 1337) == 1379); 

return 0; 

这意味着您可以轻松地开始将较大的C程序的某些部分转换为Zig并继续进行编译。 移植程序时,这是一项非常强大的功能。 过去让我很容易地从Objective-C移植到Swift的原因是,我可以一次用Swift版本替换一个Objective-C方法,进行编译并看到一切仍然有效。

实际上,通过允许您自动将C程序转换为Zig代码,Zig使其变得更加容易。 这是Zig编译器内置的:

$ zig translate-c foobar.c 

当然,该代码不是最佳的,可能有点混乱。但这有点像使用Google翻译进行自然语言翻译。这是一个很好的起点,可以节省大量的体力劳动。您可以稍后自己手动修复细节。

极简主义

极简主义首先吸引了很多人使用C编程。 这就是Go正确的事情,并使编程变得很高兴。 您可以轻松地将整个程序放在脑子里。

现在,如果您开始阅读Zig并查看我在这里给您的源代码示例,它可能看起来很复杂。 有些语言结构可能看起来很奇怪。 可以很容易地感觉到它是一种复杂的语言。

因此,弄清Zig不支持的所有事物实际上非常有用:

  • 没有类继承,例如C ++,Java,Swift等。
  • 通过Go之类的接口没有运行时多态性。
  • 没有泛型。 您不能像在编译时检查的Swift中那样指定通用接口。
  • 没有函数重载。您不能多次使用不同的参数编写具有相同名称的函数。
  • 没有异常抛出。
  • 没有闭包。
  • 没有垃圾收集。
  • 没有用于资源获取的构造函数和析构函数是初始化(RAII)。

但是,通过巧妙地使用一些核心功能,Zig能够提供几乎相同的功能:

  • 类型可以在编译时像对象一样传递。
  • 标签工会。在其他编程语言中也称为求和类型或变体。
  • 函数指针。
  • 实数指针和指针算术。
  • Zig代码可以在编译时部分评估。您可以使用comptime关键字将代码标记为在编译时可执行。
  • 函数和类型可以与结构相关联,但不能物理存储在结构中,因此C代码不可见。

在Zig中模拟泛型

例如。通过利用在编译时运行代码的能力,在Julia中创建了类似于模板的内容。洛里斯·克罗(Loris Cro)有一篇很好的文章更详细地描述了这一点。我将仅使用该文章中的示例来快速了解该想法。

我们可以定义例如一个称为LinkedList的函数,该函数只能在编译时调用,该函数采用链表中元素的类型,然后返回包含以下元素的链表类型:

fn LinkedList(comptime T: type) type { 

return struct { 

pub const Node = struct { 

prev: ?*Node = null, next: ?*Node = null, data: T, 

}; 

first: ?*Node = null, last: ?*Node = null, len: usize = 0

}; 

这利用了结构可以是匿名的事实。 您不需要给他们起个名字。 但是,此功能需要一点包装。 注意这一部分:

pub const Node = struct { prev: ?*Node = null, next: ?*Node = null, data: T,}; 

这里有许多Zig特定功能在起作用,需要一些解释。 在Zig中,可以在定义结构时将值分配给结构成员。 成员可以是在编译时或运行时存在的字段。 上一个:?* Node = null是结构字段的一个示例,该字段在编译时存在,但其默认值为null。 那疯狂的*前缀呢?

在Zig中,* Node表示类似于C / C ++的指向Node类型对象的指针。但是,由于Zig除非明确允许,否则不允许指针为null,因此必须添加?。指示指针可以为空。

节点本身被设置为周围匿名结构的字段。 但是,由于将其定义为const,因此仅在编译时存在。 如果在运行时检查LinkedList结构的内存,则找不到与Node对应的区域。

另外请记住,虽然您可以在编译时将类型用作任何其他对象,但它们在运行时在Zig中并不存在。 因此,基本上我们在这里所做的就是创建带有嵌套类型的结构。

让我使用Loris Cro的示例之一进行更好的解释。首先,他创建一个包含点的链表,并将其分配给仅在编译时存在的名为PointList的变量:

const PointList = LinkedList(Point); 

然后,我们可以使用此新创建的类型实例化一个空列表。

var my_list = PointList{}; 

我们不需要为first,last和len指定任何初始值,因为它们具有默认值。

在这里,我们使用嵌套类型创建一个Node对象来保存我们的点数据:

const p = Point.x = 0.y = 2.z = 8 }; 

var node = PointList.Node{ .data = p }; 

my_list.first = &node; 

my_list.last = &node; 

my_list.len = 1

在Zig中模拟接口

尽管Zig没有类或面向对象语言之类的接口的关键字,但我们仍然可以构建自己的运行时多态系统,类似于C程序员多年来所做的那样。

您只需使用函数指针定义结构即可。在Unix内核中,您看到了类似的操作,可以对任何文件描述符进行通用处理,无论它们是文件,套接字还是管道。

typedef struct _File { 

void (*write)(void *fd, char *data); 

void (*read)(void *fd, char *buffer, int size); 

void (*close)(void *fd); 

File; 

这并不完全是它的定义方式。 我只是从内存中做到这一点。 这允许我们做的是为文件,套接字和管道提供不同的打开功能。 但是,由于它们都给了我们File结构,因此其他函数可以使用其包含的这些函数指针对此进行操作,从而抽象出底层结构的差异。

在Zig中,我们使用Nathan Michaels在此处更详细描述的类似方法。 Zig比C提供了更好的功能,因此您会看到Zig在创建通用迭代器,分配器,读取器,写入器以及在Zig中进行更多操作时使用了更多功能。

有人可能会问,为什么不将这类内容纳入语言呢?如果您曾经使用过Lua,那么您将了解一些优点,而不是给您构造块以创建面向对象的系统,而不是对其进行硬接线。

使用Zig,您可以构建C ++风格的面向对象的系统,类似于Go甚至是类似于诸如Objective-C之类的更动态语言的面向对象编程。

这种自行开发的方法可能非常有效。我们已经看到LISP程序员使用它来在LISP中构建面向对象的编程系统,甚至创建类似于Julia的多调度系统。

延伸 · 阅读

精彩推荐
  • 编程技术从Context源码实现谈React性能优化

    从Context源码实现谈React性能优化

    这篇文章主要介绍Context的实现原理,源码层面掌握React组件的render时机,从而写出高性能的React组件,源码层面了解shouldComponentUpdate、React.memo、PureComponen...

    魔术师卡颂5312020-12-20
  • 编程技术真正聪明的程序员,总有办法不加班

    真正聪明的程序员,总有办法不加班

    工作效率提升了,就可以少加班了,聪明的程序员,总会有一堆可以提升编码效率的工具?当一种工具满足不了工作需求,就去探索新的,今天纬小创就给...

    今日头条12482021-03-04
  • 编程技术用户态 Tcpdump 如何实现抓到内核网络包的?

    用户态 Tcpdump 如何实现抓到内核网络包的?

    在网络包的发送和接收过程中,绝大部分的工作都是在内核态完成的。那么问题来了,我们常用的运行在用户态的程序 tcpdump 是那如何实现抓到内核态的包...

    开发内功修炼11612021-09-08
  • 编程技术让开发效率倍增的 VS Code 插件

    让开发效率倍增的 VS Code 插件

    今天来分享一些提升开发效率的实用 VS Code 插件!Better Comments 扩展可以帮助我们在代码中创建更人性化的注释,有不同形式和颜色的注释供我们选择。 ...

    前端充电宝7132022-04-21
  • 编程技术2021年值得关注的React PDF 库

    2021年值得关注的React PDF 库

    今天,许多网络应用程序为其用户提供内置的PDF浏览选项。然而,选择一个并不容易,因为它们的功能远远超过显示PDF。在这篇文章中,我将评估5个React的...

    TianTianUp5222021-06-21
  • 编程技术Delphi - Indy idMessage和idSMTP实现邮件的发送

    Delphi - Indy idMessage和idSMTP实现邮件的发送

    这篇文章主要介绍了Delphi - Indy idMessage和idSMTP实现邮件的发送,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...

    JJ_JeremyWu6592020-09-22
  • 编程技术简单、好懂的Svelte实现原理

    简单、好懂的Svelte实现原理

    本文会围绕一张流程图和两个Demo讲解,正确的食用方式是用电脑打开本文,跟着流程图、Demo一边看、一边敲、一边学...

    魔术师卡颂4822021-11-10
  • 编程技术AIOps,SRE工程师手中的利器

    AIOps,SRE工程师手中的利器

    AIOps开始成为一种极为重要的站点可靠性工程工具。它能够高效吸纳观察数据、参与数据以及来自第三方工具的数据,判断系统运行状态并保证其处于最佳...

    至顶网5962021-03-08