你好,“你好,世界”

2020-08-16 17:20:50

2020年8月15日语言的最初评判通常是在它们的“你好,世界!”节目上。它写起来有多容易?去跑步吗?这有多容易理解呢?这是一个非常简单的程序,当然,是最简单的,甚至是…。只需制作一小段文字,并将其显示出来,还有什么比这更简单的呢?

通过如此粗略的印象来评判一门语言确实不公平,但它可以让你了解一门语言的价值以及它是如何发挥作用的。语法是什么样子的?打好了吗?这是翻译的吗?通常你一眼就能看出很多东西。

通常,来自解释型语言的人会立刻体验到编译的、系统的语言变得更加复杂。编译和运行是分开的步骤,这显然增加了复杂性,而不是简单地将可执行文件指向某些源代码并立即看到结果,但通常会有与之相伴随的句法结构。

您会注意到这里面也有一个宏。这要付印了!它可以扩展到调用到。

Fn<;T&>;(args:fmt::Arguments<;&39;_>;,local_s:&;<;RefCell<;Option<;Box<;dyn Write+Send>;>;,global_s:fn()->;T,Label:&;Str,)WHERE T:write,{let result=local_s.try_with(|s|{//注意,我们完全删除了要写入的本地接收器,以防//我们的打印//递归死机/打印,因此递归//死机/打印将转到全局接收器,而不是本地接收器。Let prev=s.borrow_mut().Take();if let ome(Mut W)=prev{let result=w.write_fmt(Args);*s.borrow_mut()=ome(W);return result;}global_s().write_fmt(Args)}).unwork_or_se(|_|global_s().write_fmt(Args));if let err(E)=result{Panic!(";,标签,e);}}。

这是,嗯,让我们这么说吧,现在看起来不是很简单吗?这里发生了很多事!

需要明确的是,我在这里根本不是在指责铁锈,我的观点实际上恰恰相反,因为在“你好的世界!”中总会有更多的事情发生,而不是Put&34;la da&34;或类似的东西会让你相信它的表面。说到Ruby的put,Ruby解释器本身(用C编写)中运行put的代码是什么?

Value(int argc,const value*argv,value out){int i,n;value line,args[2];/*如果未给出参数,则打印换行符。*/if(argc==0){rb_io_write(out,rb_default_rs);return Qnil;}for(i=0;i<;argc;i++){if(rb_type_P(argv[i],T_string)){line=argv[i];转到字符串;}if(rb_exec_recsive(io_put_ary,argv[i],out)){Continue;}line=Rb。字符串:n=0;args[n++]=line;if(RSTRING_LEN(Line)==0|!rb_str_end_with_asciichar(line,';\n';)){args[n++]=rb_default_rs;}rb_io_writev(out,n,args);}return qnil;}

我们都知道,像Ruby或Python这样的语言就是专门设计用来对我们隐藏这种复杂性的,让我们继续处理数据块、服务web请求或解决sudokus之类的肮脏事情,谢天谢地,但是哇,这可真是太多了,不是吗?

当人们从被设计成符合人体工学的语言到更多面向系统的语言时,他们经常会被他们认为的不优雅、丑陋和冗长所震惊。可以肯定的是,有时确实是这样的..。(尽管任何在产品代码库中使用过漂亮语言的人都知道,这些描述符也不能幸免)。

通常情况下,权衡是明确的:优雅和简单的控制…。对最终运行的程序进行具体的、细粒度的控制。对你的程序有那么大的控制权并不总是必要的,事实上几乎总是没有必要的。显然,生产率很重要,如果你的业务是插入可行的业务,那么你的目标很可能不会通过整天忙于手动内存管理而得到最佳实现(至少从宏观层面来看,从一般意义上来说是这样)。

但是如果你确实需要这种控制呢?那好吧,你需要它。当实际需要每一项性能时,或者在具有硬内存限制的嵌入式系统上,或者在为一些定制的或其他不常见的处理器编写代码时。

我将选择一种语言--Zig,然后深入研究它的hello world,但这里需要注意的是,我主要要说的不是Zig,而是所有语言都必须面对巨大的复杂性才能做任何事情,即使是像hello world程序这样最简单的任务。复杂性,这在很大程度上是隐藏在我们今天的今天。那么,打招呼的世界到底是怎么回事呢?

Const std=(";std&34;);pub fn()!Void{const stdout=std.io.getStdOut().outStream();尝试stdout.print(";Hello,{}!\n";,.{";world";});}。

如果您是zig的新手,那么在我详细介绍这个语法之前,先简要介绍一下这个语法。

@import是一个编译器内置函数,它将其引用的文件的名称空间分配给左侧的常变量。

就像在C中一样,main是一个特殊的函数,它在将程序编译为可执行文件后将其标记为入口点。与C不同,它接受无参数(C';的main函数有各种变数,这使得它有点独特),并且命令行输入通过实用程序函数可用,以便更容易地跨平台使用。

标记为pub以便可以从Immediate模块(此处指的是当前命名空间的顶级作用域...即文件)之外访问它,这是必要的步骤,因为作为程序的入口点,main必须可以从Immediate作用域外部访问。

Main()是函数的名称(以及参数列表所在的位置),!void是返回类型。仔细查看该返回类型:

在C中,函数的返回类型在任何其他类型之前声明。这在一定程度上是有道理的:毕竟,它与变量的声明方式是一致的,而且扫描文件你可以清楚地看到,调用这个将会得到这样的结果。

在Zig中,返回类型在函数声明之后但在函数体之前。这也说得通!在“生锈与死亡”中也是如此,而且似乎通常是一种更现代的方法。原因实际上非常简单:这样做可以获得上下文无关的语法!C和C++将解析器置于这样一种位置,即它必须理解语义才能仅仅解析源代码。

在Zig中,main返回void(实际上,它可以返回各种内容,如果它返回void(这只是说明它根本不返回任何内容)),那么它实际上是作为一个成功代码返回0,但是)有一个问题!Void前面有一个感叹号。这意味着:";该函数应该返回void,但它可能会失败并返回错误。";这是一个推断的错误集,每当调用可能失败的函数时,编译器都会强制您在调用点处理该错误。更多关于Zig';的错误处理的其他时间,现在这是足够了解什么!在返回类型声明装置前面。我想转移到函数的正文部分,让我们逐行讨论戈林。

因此,我们可以看到,这是对标准库函数(STD)的调用,该函数返回我们分配给const stdout的内容。标准输出(Stdout)和标准错误(Stderr)可能是shell中熟悉的概念,但是在此程序中引用标准输出是什么意思呢?标准输出到底是什么?无论它是什么,它都是通过调用outstream()返回的,而outstream()是在std.io.getStdOut()的返回值上调用的方法,所以我们首先需要知道它是什么。

追根溯源!在Zig源代码树中,std位于lib/std/std.zig中,该文件提供了多种功能。它包括一行:

它在std变量中称为std.io(同样,请注意pubkeyword,如果没有它,这个声明的常量将无法在这个直接作用域之外访问)。深入到lib/std/io.zig...。

Pub fn()文件{Return File{.Handle=getStdOutHandle(),.capable_io_mode=.block,.intination_io_mode=default_mode,};}。

因此,stdout是一个File结构。让我们来看看这个。它在io.zig的顶部导入为。

并位于源代码中,也许并不令人意外,它位于lib/std/fs/File.zig。此结构定义相当长,因此我将重点介绍我们想要查看的内容,即outstream()方法。

Zig没有真正的方法,但是将特殊的函数类作为方法来讨论是很有用的,因为调用约定支持使用点语法在结构实例上调用时隐式传递self。让我告诉你我的意思。

Const std=(";std";);const thing=struct{instanceVariable:u8,const classVariable=41;fn(y:u8)u8{return classVariable+y;}fn(self:thing)u8{return self.instanceVariable;}};pub fn()!Void{std.debug.warn(";{}\n";,.{Thing.staticMethod(1)});//42 const thing=thing{.instanceVariable=1};std.debug.warn(";{}\n";,.{thing.instanceMethod()});//1}。

因此,尽管缺少显式的类,但由于支持这种调用约定,这些模式是可用的。对待类似于类定义的结构,您可以对结构定义本身调用静态方法。在上面的示例中,

Ruby中的语法。事实上,Ruby中的等效示例看起来与Zig版本非常相似:

Class attr_accessor:instanceVariable@@classVariable=41 def(InstanceVariable)@instanceVariable=instanceVariable end def。(Y)@@classVariable+y end def()@instanceVariable end ENDP Thing::staticMethod(1)#42thing=Thing.new(1)p thing.instanceMethod#1。

当然,这里有显著的不同之处!尝试在Ruby中的类实例上调用staticmethod。

Ruby是一种完全面向对象的语言,因此它的底层类抽象当然比Zig中的这个传真件更健壮,但其效果是,好吧,关于azig&34;instance&34;static&34;方法实际上没有什么特别之处,因为它们只是在struct上定义的函数,碰巧可以通过多个调用约定使用。

但是它会告诉您,您向该方法传递了一件东西。这是重要的一点:实例方法有特殊的访问";实例变量";的权限,因为它们引用了它们正在调用的结构,也就是Sall。这就是这里所有的魔力。

还要注意,单词';self';没有什么特别之处,它只是一个常规的变量名。

但是,为了强调这里没有什么神奇的事情发生,您确实可以这样做:

对instanceMethod的这两个调用是相同的,但具有不同的调用约定(因此第一个调用隐式传递self!)。

这将返回一个外流结构,该结构使用被调用的File的Self进行初始化(这里称为file)。ZIG支持匿名结构文字,在这种情况下能够根据函数的返回值推断类型。还要注意匿名结构文字以.开头的奇怪语法,这是为了在语法上将其与块区分开来。

嗯,这很有趣……。此函数调用是否返回...。类型定义?然后将其分配给Outstream并用作pub FN Outstream的返回值?

Pub fn(comptime context:type,comptime WriteError:type,comptime writeFn:fn(context:context,bytes:[]const U8)WriteError!Usize,)type{return struct{context:context,//...}}。

这是Zig';支持泛型的方式!给定一些编译时已知值,您可以在编译时动态创建结构定义。这里有一篇关于该功能的更详细的帖子:什么是Zig&#sComptime?

现在,请注意write正在作为writeFn参数传递给io.outStream,它最终将被调用以打印到标准输出。

我们最终得到了一个外流结构,其上下文字段初始化为由std.io.getStdOut()返回的File结构。

Pub fn(self:self,comptime format:[]const U8,args:var)错误!Void{return std.fmt.format(self,format,args);}。

好,越来越近了:在本例中,OUT_STREAM是开头的File From Way Back。

安迪说:差不多就是file.outStream()返回值。它只是将WRITE函数作为类型的一部分的";上下文";。目前,流在ZIG中的工作方式是鸭子打字。它优化得很好,API大部分都很好,但它会产生臃肿的代码,而且在某些情况下,API过于泛型,令人恼火。有时接受非类型作为流参数会更好。";

另外两个参数是在顶层调用站点传入的,一个字符串常量和一个匿名listtext(其行为与前面提到的匿名结构体相似),它们的位置参数将在{}标记的点处插入到格式化字符串中。您可以传入格式选项,非常类似于c##;的printf。

Format是一个很长的函数,有很多记账操作,但其中大部分是它对out_stream.writeAll的调用。回到那个定义:

Pub fn(self:file,bytes:[]const U8)WriteError!Void{var index:usize=0;while(index<;bytes.len){index+=try self.write(bytes[inde..]);}}。

Pub fn(self:file,bytes:[]const U8)WriteError!Usize{if(Is_Windows){return windows.WriteFile(self.handd,bytes,null,self.devented_io_mode);}Else if(self.capable_io_mode!=self.inventent_io_mode){return std.event.Loop.instance.?.write(self.Handle,Bytes);}Else{return os.write(self.Handle,Bytes);}}。

现在,最后,我们再回到系统编程的系统,这种方法的操作方式不同,取决于它正在使用的系统!在此文件lib/std/fs/file.zig的顶部,

我不在Windows上,现在我将忽略第二个分支,所以我不必进入异步(这完全是另一回事!),所以我在这里结束:

我正在调用特定于OS的库函数,该函数接受写入字节的位置和要写入的字节(此时用来自调用点的那些插入值格式化)。这就是它的用武之地。

Pub const system=if((root,";os";)and root.os!=())root.os.system Else if(builtin.link_libc)std.c Else switch(builtin.os.tag){.macosx,.ios,.watchos,.tvos=>;Darwin,.frebsd=>;FreeBSD,.linux=>;linux,.netbsd=>;linux,.netbsd=&。WASI,.windows=>;windows,否则=>;struct{},};

Pub fn(fd:i32,buf:[*]const U8,count:usize)usize{return syscall3(.write,(usize,(isize,fd)),(Buf),count);}。

Pub使用命名空间开关(builtin.arch){。I386=>;(";linux/i386.zig";),.x86_64=>;(";linux/x86_64.zig";),.aarch64=>;(";linux/arm64.zig";),.arm=>;(";linux/arm-eabi.zig";),.riscv64=>;),.mips,.mipsel=>;(";linux/mips.zig";),Else=>;struct{},};

这里,syscall3中的";3";是指调用syscall所需的";参数";的数量。

Pub fn(number:sys,arg1:usize,arg2:usize,arg3:usize)usize{return ASM Volatile(";syscall";:[RET]";={rax}";(->;usize):[number]";{rax}";((Number)),[arg1]";{RDI}";(Arg1),[arg1。{rsi}";(Arg2),[arg3]";{rdx}";(Arg3):";rcx";,";r11";,";内存";);}。

这接近但不完全是编译器为此调用实际发出的值。令人惊讶的是,即使在这里,我们看到注册名被直接调用,仍然有一层抽象层,编译器在其中有一些回旋余地。

现在不是那么简单的程序了,是吗?请记住,这些syscall对于每个体系结构都是不同的,编译器会根据您重定位的内容生成机器码,所以这只是许多可能的路径之一。我认为,当你仔细查看细节时,很容易忘记这件事可能很快就会变得多么复杂。

现在让我从一个稍微不同的角度来看这个。我们知道,Zig编译器的工作,就像任何编译器一样,是获取源代码并将其转换成其他东西。ZIG具有很高的可移植性;使用llvm作为后端方法基本上可以针对llvm所针对的任何对象,但需要注意的是,没有任何库函数在所有平台上都有相同数量的支持(有关这方面的更多详细信息,请参阅可支持的部分)。

所以,有很多可能的目标,所以也有很多其他的可能的东西,可以让源变成其他的东西。但是让我们来看看最明显的例子:构建一个针对我当前正在运行的系统的可执行文件。

编译器内部的转换管道从源->;中间表示->;目标开始,其中中间表示可以是很多东西,并且包括很多步骤。事实上,ZIG有自己的IR,在将其转换为LLVM IR并将其传递到可以通过许多可能的优化和编译/汇编步骤进行处理的位置之前,它会运行自己的静态分析过程(LLVM称之为这些遍)。对我来说,目标是x86_64机器码,但在此之前的最后一站,从概念上讲,实际上也很可能是汇编本身。

因为clang是建立在llvm上的完整编译器工具链,而Zig可以用来替代clang,所以我们应该能够使用zig cc将汇编代码直接编译成机器代码。这一步实际上被称为汇编,而不是编译,并且是由汇编器而不是编译器来完成的,但是tbqh和imho这些是没有太大区别的区别。

使用锯齿形抄送有什么好处?主要是您能够可靠地使用您正在使用的zig版本所依赖的相同工具链和llvm版本。不要在系统库和链接上胡乱摆弄,它应该已经准备好工作了。

Clang足够聪明,可以通过其扩展名检测文件类型,zig cc也是如此。

Zig:警告:编译期间未使用的参数:';-nostdinc';[-Wunuse-command-line-argument]zig:警告:编译期间未使用的参数:';-fno-拼写检查';[-Wunuse-command-line-argument]zig:警告:编译期间未使用的参数:';-fno-omit-frame-Pointer';[-Wunuse-command-line-argument]zig:警告:[-WunUsed-Command-line-Argument]zig:Warning:编译期间未使用的参数:';-fstack-Protector-strong';[-Wunuse-command-line-Argument]Zig:警告:编译期间未使用的参数:';--param ssp-buffer-size=4';[-Wunuse-command-line-Argument]Zig:警告:编译期间未使用的参数:';-issystem/usr/local/include&。[-Wunuse-command-line-argument]zig:警告:编译期间未使用的参数:';-issystem/usr/include/x86_64-linux-gnu';[-Wunuse-command-line-argu&39;[-Wunuse-command-line-参数]zig:警告:编译期间未使用的参数:';-issystem/usr/include';[-Wunuse-command-line-argument]lld:错误:未定义的符号:main>;>;>。由启动引用。S:104(/home/jfo/code/zig/build/lib/zig/libc/glibc/sysdeps/x86_64/start.S:104)>;>;>;/home/jfo/.cache/zig/stage1/o/ujWleITFBRHwV19Tq0gsSK_F_gRDc7-jgOCip86Un1bhdmx0pIXLGQRxtjMtuntC/Scrt1。

.