Uploaded avatar of iHiD

É o Mechanical March!

@iHiD
Mais de 3 anos atrás
Vídeo

Bem-vindo ao nosso segundo mês temático: o Mechanical March. Neste mês, o foco são as linguagens de sistema, aquelas que compilam para código de máquina.

Este texto é parte post, parte transcrição do vídeo do Mechanical March. Vou fazer uma introdução rápida ao mês e depois vamos olhar um pouco para as linguagens de sistema, sua evolução ao longo da história, os prós e contras de compilar para código de máquina, e dar uma olhada rápida em cada uma das linguagens em destaque. Estou acompanhado de novo pelo Erik, que vai falar a maior parte do tempo na segunda metade. Mas eu começo com algumas informações práticas.

Então, primeiro, as linguagens em destaque neste mês. São C, C++, D, Go, Nim, Rust, V e Zig. Para ganhar o selo do Mechanical March, você precisa completar cinco exercícios em uma dessas linguagens. Nossa trilha de Go tem uma das melhores grades curriculares do Exercism, então eu recomendo muito que você experimente. Também somos grandes fãs do Nim aqui no Exercism, porque é uma linguagem relativamente simples para começar e muito fácil de escrever, então eu com certeza recomendo experimentar também.

Também temos cinco exercícios em destaque para você experimentar:

  • Linked-list ou simple-linked-list (dependendo da linguagem): alocar/liberar memória, ponteiros
  • secret-handshake: operações bit a bit
  • pangram: laços for, strings e caracteres
  • sieve: arrays, laços for
  • binary-search: arrays, laços

Tem um selo novo que eu anunciei no vídeo de atualizações que fiz alguns dias atrás, para quem completa os cinco exercícios em destaque nas linguagens temáticas. Então, para ganhar esse selo, você vai precisar completar todos esses exercícios em uma linguagem de sistema em algum momento do ano.

Também temos um monte de coisas legais borbulhando por aí: entrevistas com algumas pessoas da equipe principal do Go, e, quem sabe, do Rust e de algumas outras linguagens também. Também vamos ter muitas transmissões ao vivo ao longo do mês. E alguns brindes do Mechanical March chegando em breve!

Então, vamos nos aprofundar um pouco mais no lado técnico.

Para que essas linguagens são usadas?

Bom, nós as usamos em todo o Exercism. Nossa CLI é escrita em Go, nossa ferramenta interna de gerenciamento de trilhas chamada configlet é escrita em Nim, e a biblioteca principal que conta as linhas de código das suas soluções é escrita em Rust. Erik, por que escolhemos essas linguagens para essas partes das ferramentas?

CLI:

  • Acho que usamos Go porque era com o que a Katrina tinha mais familiaridade.
  • Go é ótimo para essas ferramentas de linha de comando mais ou menos pequenas.
  • O código Go é relativamente direto, o que facilita as contribuições
  • Os binários Go são fáceis de publicar, porque não exigem runtime
  • Go faz compilação cruzada muito bem

Nim

  • Nim tem a maior parte das mesmas vantagens do Go
  • Não tínhamos muitas pessoas que conheciam Go e podiam manter a CLI, então mudamos para Nim

Em que outros tipos de lugares você esperaria encontrar essas linguagens sendo usadas?

  • Em qualquer lugar onde o desempenho é importante (drivers, jogos, sistemas operacionais, sistemas de build/compiladores)
  • Em qualquer lugar onde os recursos são limitados (por exemplo, software embarcado)
  • Em qualquer coisa que precise ser altamente portável, ou seja, rodar em muitas plataformas diferentes

O que é código de máquina?

Como eu disse antes, essas linguagens do Mechanical March se destacam por compilar para código de máquina. Você pode explicar um pouco o que é código de máquina e, em contrapartida, o que é bytecode?

  • Código de máquina é um código que roda diretamente na máquina.
  • O bytecode, em contrapartida, exige outro código para interpretar/compilar o bytecode em código de máquina. Portanto, o bytecode exige um passo intermediário antes de poder ser executado.

Quais são os prós e contras do código de máquina em relação ao bytecode?

Prós:

  • Inicialização mais rápida (sem etapa de compilação JIT)
  • Menor consumo de memória (nenhum runtime sendo carregado, nenhum bytecode na memória, ótimo para sistemas embarcados)
  • A máquina de destino não precisa ter um runtime instalado (importante para manter contêineres Docker pequenos)

Contras:

  • Não é portável. O bytecode é portável, mas o código de máquina compilado é específico de cada plataforma
  • Não permite otimizações avançadas, como a otimização guiada por perfil (determinar a melhor forma de compilar o código (bytecode) depois de executá-lo por um tempo)

Observação: abordagens híbridas são possíveis, em que a linguagem compila para bytecode e depois usa uma ferramenta diferente para compilar esse bytecode em código de máquina.

A evolução da programação de sistemas

Ok. Vamos olhar um pouco para a evolução de algumas das linguagens deste mês. Primeiro, vamos começar lá atrás, com o C, e ver como o C++ evoluiu a partir dele. Fale um pouco sobre essas linguagens.

C é uma linguagem de muito baixo nível. É quase como se você estivesse um pouco acima do código de máquina. Isso a torna muito poderosa e altamente otimizável, mas também um pouco propensa a bugs (por exemplo, exceções de ponteiro nulo e estouros de buffer). O gerenciamento de memória é todo manual e, portanto, responsabilidade de quem programa, o que pode levar a bugs e/ou vazamentos de memória. C++ é como C, mas com suporte a programação orientada a objetos. Continua sendo bem de baixo nível e exige que você faça o gerenciamento manual de memória. Tanto C quanto C++ permitem escrever assembly inline (ASM)!

E quanto às linguagens de sistema mais novas? Como elas evoluíram?

Todas as linguagens de sistema modernas suportam gerenciamento automático de memória, seja por contagem de referências, por um coletor de lixo ou por algum outro mecanismo.

As primeiras linguagens de sistema tinham suporte a ponteiros nulos (o que Tony Hoare chamou de seu "erro de um bilhão de dólares"). Eles são famosos por causar erros em tempo de execução e vulnerabilidades. Muitas linguagens modernas abrem mão do null, ou pelo menos exigem esforço para usá-lo.

Outra mudança é a passagem de valores mutáveis como padrão para valores imutáveis por padrão. Por exemplo, Rust e VLang têm valores imutáveis por padrão, exigindo que você opte pela mutabilidade.

Todas as linguagens mais novas têm suporte a interoperabilidade com C (ou C++), já que muito código foi escrito nessas linguagens.

Outra coisa interessante é que algumas das linguagens mais novas não compilam diretamente para código de máquina, mas usam outras ferramentas para isso. Por exemplo, Rust e Zig usam LLVM, enquanto Nim permite usar uma grande variedade de compiladores. Isso é conhecido como transpilação.

E quanto a coisas como macros e metaprogramação?

Há uma divisão interessante em relação a macros/metaprogramação. As macros em C/C++ são poderosas, mas têm uma reputação um tanto duvidosa de serem difíceis de usar. Rust, Nim e D oferecem metaprogramação poderosa, mas de uma forma muito mais agradável. Por outro lado, VLang e Zig mencionam explicitamente a ausência de macros como uma característica da linguagem, e Go tem uma abordagem diferente com o go generate.

As linguagens de sistema têm a reputação de serem bem de baixo nível. Isso ainda é justo?

As linguagens mais novas trabalham em níveis de abstração mais altos do que C/C++. Por exemplo, Rust, D e Nim também permitem uma forma bem funcional de escrever código. Nim e D têm até o conceito de funções "puras".

Introdução às linguagens do mês

Então seria bom olhar para cada uma das linguagens, uma por uma. Todas as linguagens têm semelhanças: todas têm tipagem forte e estática. Mas vamos ver como elas variam. Começamos com C?

C

  • Desenvolvida por Dennis Ritchie
  • Uma das mais antigas e provavelmente a linguagem mais usada do mundo
  • Uma enorme quantidade de software é escrita em C, como Unix e Linux
  • Muito influente (pense em: linguagens no estilo C existirem)
  • Gerenciamento manual de memória
  • Alto desempenho (próxima do "metal")
  • Roda em qualquer lugar
  • Perfeita para sistemas embarcados
  • Linguagem bem pequena

C++

  • Desenvolvida por Bjarne Stroustrup
  • Sucessora do C, mas com orientação a objetos adicionada (C com classes)
  • Ajudou a popularizar a programação orientada a objetos
  • Mais recursos de alto nível do que C
  • Suporta programação genérica por meio de templates
  • Adiciona suporte a módulos por meio de namespaces
  • Muitos jogos (e engines) são escritos em C++, e grandes partes do Windows
  • Gerenciamento manual de memória
  • Continua evoluindo, com novos recursos sendo adicionados regularmente (especificação enorme)

D

  • Desenvolvida por Walter Bright, e depois com a participação de Andrei Alexandrescu
  • Originalmente pensada como um C++ reprojetado (aprendendo com seus "erros"), ela se inspira em muitas outras linguagens
  • Multi-paradigma, com suporte a programação imperativa/orientada a objetos/funcional
  • Interoperabilidade fácil com C/C++
  • Sintaxe Uniforme de Chamada de Função
  • Avaliação de Função em Tempo de Compilação (por exemplo, gerar uma máquina de estados de Regex em tempo de compilação)
  • Suporta programação funcional e funções "puras"
  • Muitos recursos de segurança
  • Segurança de memória via @safe
  • Contratos (pré/pós-condições, invariantes)
  • Funções puras
  • Foco em testes de unidade, com os testes ao lado do código-fonte que testam (o que exigiu uma exceção no site do Exercism :))
  • Unicode

Rust

  • Desenvolvida pelo funcionário da Mozilla Research Graydon Hoare, depois adotada oficialmente pela Mozilla e agora parte da Rust Foundation
  • Multi-paradigma, com suporte a POO (mas de forma opinativa, por exemplo, sem herança), programação imperativa e funcional (tipos Option/Result, pattern matching)
  • Muitas ferramentas novas escritas em Rust (por exemplo, SWC, mas também Gleam, e a segunda linguagem com suporte no kernel do Linux; dependemos dela para o contador de linhas de código)
  • Foco em confiabilidade e desempenho
  • Construída sobre LLVM
  • Linguagem mais amada na pesquisa do StackOverflow nos últimos 7 anos
  • Rápida, em parte devido a um núcleo e uma biblioteca padrão mínimos
  • Segura, tanto em segurança de memória quanto de threads, por meio de ownership e lifetimes, imutável por padrão
  • Sistema de tipos poderoso, que detecta muitos bugs em tempo de compilação (especialmente os relacionados à memória). O compilador exibe mensagens de erro muito úteis
  • Tudo já vem incluído: compilador, ferramenta de build, formatador, gerenciador de pacotes, integrações com IDEs
  • Documentação excelente (há até um documento enorme sobre como o compilador funciona)
  • Portável: compila para um único binário estático e não exige runtime instalado
  • Interoperabilidade fácil com código C
  • Abstrações de custo zero
  • Concorrência sem medo
  • Macros

Nim

  • Desenvolvida por Andreas Rumpf (originalmente chamada Nimrod)
  • Sintaxe inspirada em Python
  • Multi-paradigma
  • Usada no Exercism no configlet
  • Ótimo desempenho: iteradores sem overhead, preferência pela alocação na pilha de tipos baseados em valor
  • Sistema de tipos moderno e expressivo: inferência de tipos, tuplas, genéricos, tipos soma, async/await
  • Coleta de lixo, mas com suporte a gerenciamento determinístico de memória (várias opções de gerenciamento de memória)
  • Execução de código em tempo de compilação
  • Sintaxe uniforme de chamada
  • Macros: fácil estender a linguagem
  • Sistema de efeitos: codificar efeitos colaterais no sistema de tipos

Go

  • Desenvolvida por Robert Griesemer, Rob Pike e Ken Thompson no Google
  • Majoritariamente imperativa/procedural, com suporte a uma abordagem parecida com orientação a objetos (mas sem herança)
  • Usada em grandes projetos como Docker e Kubernetes. Também é ótima para backends e CLIs (por exemplo, esbuild)
  • Pretende ser simples o bastante para caber na cabeça (pouca sintaxe)
  • Segurança de memória por meio do coletor de lixo
  • Rápida: compilação rápida, testes rápidos e execução rápida. Suporte nativo para escrever benchmarks!
  • Opinativa: muito esforço para influenciar o estilo do código Go: pouca sintaxe, formatação de código via go fmt, ferramentas para verificar o uso idiomático do código, erro em coisas que são avisos em outras linguagens (por exemplo, variáveis não utilizadas), documentação que lista as expressões idiomáticas do Go
  • Sistema de tipos leve que torna Go muito flexível (com suporte a inferência de tipos)
  • Portável: compila para um único binário estático e não exige runtime instalado. Compilação cruzada fácil. Concorrência via goroutines (leves) e comunicação via canais
  • Tipagem estrutural via interfaces (parecido com duck typing, mas verificado estaticamente)
  • Tratamento de erros: a linguagem incentiva verificar e tratar os erros

VLang

  • Desenvolvida por Alexander Medvednikov e Delyan Angelov
  • Inspirada em Go:
  • Mesma estratégia de "só uma forma de fazer as coisas"
  • Mesma estratégia de "sintaxe mínima"
  • Corrotinas
  • Diferente do Go:
  • Sem nil/null, mas usa tipo de resultado
  • Imutável por padrão
  • Tipos soma (funcional)
  • Interpolação de strings
  • Runtime/binários menores
  • Pattern matching
  • Trabalhando para tornar o GC opcional (autofree)
  • Interoperabilidade com C de custo zero
  • Gera documentação a partir do código
  • Compilador rápido que usa pouca memória

Zig

  • Desenvolvida por Andrew Kelley
  • Pouca sintaxe (arquivo de gramática PEG de 500 linhas)
  • Execução de código e reflexão em tempo de compilação
  • Pretende ser "óbvia", sem fluxo de controle, alocações ou macros/metaprogramação escondidos
  • Alocação manual de memória
  • Suporta diferentes alocadores
  • Funções da biblioteca padrão que alocam têm um parâmetro de alocador
  • O framework de testes consegue detectar vazamentos de memória
  • Segurança:
  • Erros são valores e precisam ser tratados
  • Sem null, usa tipo opcional
  • Os testes podem ser escritos no mesmo arquivo que o código-fonte (como em D)
  • Usa LLVM como backend
  • Consegue compilar código C/C++
  • Interoperabilidade fácil com C
  • Compilação cruzada fácil

Conclusão

Vamos parar por aqui, porque imagino que a cabeça de todo mundo já esteja bem cheia a essa altura.

Espero que esta tenha sido uma introdução útil e divertida às linguagens em destaque deste mês. Espero que você se divirta muito explorando essas linguagens. Eu e o Erik vamos adorar saber quais você escolher e o que achou delas, então não deixe de comentar aqui embaixo ou no fórum!

Obrigado por assistir!

01 de março de 2023 · Achou útil?