É muito comum que os desenvolvedores mudem de uma linguagem de programação para outra. Por exemplo, você pode converter um aplicativo JavaScript em TypeScript para aproveitar as vantagens da capacidade de manutenção e reutilização do código deste último.

Você sabia que também pode fazer isso com o Rust? Rust e TypeScript trazem suas próprias vantagens e desvantagens, e aprender a escrever aplicativos em Rust e TypeScript ajudará você a se tornar um desenvolvedor melhor e mais versátil.

Neste tutorial, mostraremos como fazer a transição de escrever aplicativos em Rust para TypeScript e vice-versa. Exploraremos como essas linguagens são semelhantes, como elas diferem e os desafios comuns associados a cada uma. Em seguida, discutiremos os benefícios de adotar o TypeScript no Rust, bem como as práticas recomendadas para fazer isso.

Aqui está o que vamos cobrir:

Rust vs. TypeScript: gerenciamento de dependências

Rust gerencia suas dependências no arquivo cargo.toml . Depois de criar seu projeto Rust, este arquivo é criado na raiz do seu projeto. Para importar uma nova dependência para seu projeto, adicione-a ao seu arquivo cargo.toml .

//arquivo cargo.toml
[pacote]
nome="exemplo @ helloworld"
versão="0.0.1"
autores=["Helloworld"] [dependências]
crono="0,4"
futuros="0,3"
atk="^ 0"

Depois de adicionar a dependência em seu arquivo cargo.toml , execute seu projeto.

A maneira como o TypeScript gerencia dependências é bastante semelhante a como o Rust gerencia as dependências. Tudo que você precisa fazer é criar um projeto TypeScript e, em seguida, um arquivo package.json será criado na raiz da pasta do projeto.

//arquivo package.json
{ "nome":"exemplo @ helloworld", "Descrição":"", "versão":"1.0.0", "scripts": { "test":"echo \"Erro: nenhum teste especificado \"&& exit 1" }, "repositório": { "tipo":"git", "url":"https://github.com/me/[email protected]" }, "palavras-chave": [], "autor":"Helloworld", "licença":"ISC", "insetos": { "url":"https://github.com/me/example@helloworld/issues" }, "homepage":"https://github.com/me/example@helloworld"
}

Em seguida, instale suas dependências. Você pode usar um gerenciador de pacotes como npm . A dependência que você instalou aparecerá no seu arquivo package.json .

Ao contrário do Rust, você não precisa importar dependências para o seu projeto editando o arquivo package.json . Quando você instala a dependência, ela é registrada em package.json . Se você adicionar a dependência em package.json sem instalá-lo (como no Rust), será solicitado que você o instale enquanto tenta executar seu projeto.

Tipos de dados em Rust e TypeScript

Rust e TypeScript são ambas linguagens tipadas estaticamente. Isso significa que o compilador tenta inferir o tipo de dados durante a compilação.

Vejamos o exemplo do Rust abaixo:

 fn main () { //NÚMEROS deixe i: i32=1; println! ("O valor de i é: {}", i);//1 //Isso gerará um erro em tempo de compilação porque 1.1 é do tipo f64 deixe j: i32=1,1; //erro [E0308]: tipos incompatíveis, esperado `i32`, número de ponto flutuante encontrado println! ("O valor de j é: {}", j);//1.1
}

Embora o TypeScript seja digitado estaticamente, ele não garante que você esteja acessando o tipo de dados correto no momento da compilação.

 const i: Número=1; console.log (`O valor de i é: $ {i}`);//1 const j: String=1,1; console.log (`O valor de j é: $ {j}`);//1.1

O programa acima gera um erro em tempo de compilação: esperado `i32`, encontrado número de ponto flutuante . No entanto, o mesmo código é executado em tempo de execução. Este ato pode causar mais problemas e pode resultar em uma exceção.

Para evitar isso, o programa abaixo usa uma biblioteca TypeScript runtypes . Ele não só dá um aviso no momento da compilação, mas também impõe a verificação do tipo de dados sempre que você está acessando um tipo diferente do que foi atribuído.

//instalar biblioteca
npm install-salvar runtypes import {booleano, número, string, literal, tupla, registro, união} de'tipos de execução'; vetor const=Tupla (número, número, número); const AcademicStaff=Record ({ tipo: Literal ('academicStaff'), localização: vetor, população: número,
}); const Student=Record ({ tipo: Literal ('aluno'), localização: vetor, população: número, pontual: booleano
}); const NonAcademicStaff=Record ({ tipo: Literal ('nonAcademicStaff'), localização: vetor, população: número, nome: String,
}); const UniversityObject=Union (AcademicStaff, Student, NonAcademicStaff); //spaceObject: SpaceObject
const universityObject=UniversityObject.check (obj);//check lançará uma exceção se o objeto não estiver em conformidade com a especificação do tipo

Blocos de código e exceções

Rust, como uma linguagem de programação estaticamente tipada, permite que as exceções sejam detectadas a tempo. Para tipos de dados, o compilador pode inferir o tipo de dados do valor que você atribuiu.

Este método ajuda a capturar erros em tempo de compilação, antes do tempo de execução. Dessa forma, você não Use exceções para controle de fluxo , o que afeta o desempenho do seu código. Você pode saber com segurança que seu programa não gerará um erro no tempo de execução depois que for compilado com sucesso.

A maneira como o Rust lida com os erros garante que não haja exceções . Para erros recuperáveis, existe um tipo Result . Quando os erros não são recuperáveis, uma macro panic! interrompe a execução do programa em tempo de compilação.

//Este erro não pode ser recuperado
fn main () { seja v=vec! [1, 2, 3]; v [99];
} //Este é um erro recuperável de ferrugem
use std:: fs:: File; fn main () { let f=File:: open ("hello.txt");
} //Um ​​programa de ferrugem típico que utiliza o pânico! para obter resultados de um erro recuperável (arquivo não encontrado)
use std:: fs:: File; fn main () { let f=File:: open ("hello.txt"); deixe f=combinar f { Ok (arquivo)=> arquivo, Err (erro)=> pânico! ("Problema ao abrir o arquivo: {:?}", Erro), };
}

Aqui está um exemplo de como o TypeScript lida com exceções com uma instrução try , catch .

//Typescript executa o programa, fecha nas exceções
tentar { try_statements
}
[catch [(exception_var)] {//e se você não estiver realmente capturando a exceção? catch_statements
}]
[finalmente { finally_statements
}]

O problema com o método try , catch é que ele lida exclusivamente com erros de tempo de execução. Portanto, o compilador não pode dizer quando há um erro em seu bloco try , catch . Além disso, você não pode usar try , catch em códigos assíncronos porque ele terminará a execução antes que o código assíncrono termine sua execução. O tratamento de erros de tipo seguro do TypeScript com type Type lida com exceções de maneira mais competente.

 =T | Erro;
tipo de exportação Tipo =Resultado ; função de exportação isError  (resultado: Resultado ): o resultado é Erro { return result instanceof Error;
} função de exportação isSuccess  (resultado: Resultado ): o resultado é T { return! isError (resultado);
} function doIt (): Result.Type  { se for verdade) { return {name:"Todd"}; } outro { retornar um novo erro ("boom") } }

Loops e instruções condicionais

Escrever instruções condicionais no TypeScript pode rapidamente se tornar ambíguo. Para resolver esse problema, a maioria dos desenvolvedores usa a instrução switch case no TypeScript. No entanto, isso também se torna prolixo com o tempo.

 tipo Opção ={_tag:'Alguns'; valor: A} | {_tag:'Nenhum'} tipo Foo={ _tag:'Foo'
} tipo Bar={ _tag:'Bar'
} tipo FooBar=Foo | Barra declare const foobar: FooBar switch (foobar._tag) { case'Some': //Chave dupla! switch (foobar.value._tag) { case'Foo': intervalo case'Bar': intervalo } intervalo predefinição: intervalo
}

O exemplo acima mostra como o TypeScript pode se tornar prolixo quando você está escrevendo instruções condicionais em ADTs aninhados. Isso ocorre porque cada nível de aninhamento requer uma instrução switch.

O mesmo exemplo pode ser reescrito em Rust, conforme mostrado abaixo:

 pub enum FooBar { Foo, Barra
} fn main () { let foobar: Option =Some (FooBar:: Foo); match foobar { Alguns (FooBar:: Foo)=> {} Alguns (FooBar:: Bar)=> {} _=> {} }
}

Any em Typescript e Unsafe In Rust

O tipo Any do TypeScript e o inseguro do Rust compartilham muitas semelhanças. Eles são usados ​​principalmente para desbloquear recursos adicionais. Por exemplo, o unsafe do Rust permite que você cancele a referência de um ponteiro bruto, acesse uma função não segura e assim por diante.

 fn main () { inseguro fn perigoso () { println! ("I'm Unsafe")//imprime I'm Unsafe } inseguro { perigoso(); } }

Com o tipo any no TypeScript, você pode acessar propriedades sem restrições, mesmo aquelas que não existem. Por exemplo, os desenvolvedores podem acessar funções e o TypeScript não verifica seu tipo:

 let Hello: any=4;
//OK, ifItExists pode existir em tempo de execução, ele não existe agora.
Hello.ifItExists ();
//OK, itExists existe (mas o compilador não verifica)
Hello.itExists ();

Do exemplo do Rust, acabamos de avaliar uma função insegura. Com este programa, Rust não impõe segurança de memória e aloca no heap. Variáveis ​​do tipo any do TypeScript também são armazenadas no heap, uma vez que o tipo de variável pode mudar no curso da execução.

Mutabilidade e imutabilidade

Um objeto ou variável mutável pode ser alterado durante o curso da execução, mesmo depois de atribuir um valor. No Rust, as variáveis ​​são imutáveis ​​por padrão porque o compilador quer garantir que você não atribua valores a uma variável mais de uma vez.

Você não precisa saber onde um valor pode mudar. Quando você executa o programa abaixo, o compilador gera um erro:

 fn main () { deixe a=5; println! ("O valor de a é: {}", a); a=6; println! ("O valor de a é: {}", a);
}

O texto tipográfico não foi projetado para imutabilidade de variável. No entanto, se você usar a palavra-chave const para atribuir valores a variáveis, não poderá atribuí-los a outro valor:

 const a=5;
console.log (a);
a=6;
console.log (a);

O programa acima irá gerar um erro em tempo de execução quando chegar a a=6; . Isso porque ele percebe que a é uma constante e outro valor não deve ser atribuído a ela.

Há momentos em que a mutabilidade é benéfica ou mesmo necessária. Você pode alterar variáveis ​​no Rust adicionando mut na frente da variável. Vejamos o primeiro exemplo que tratamos:

 fn main () { deixe mut a=5; println! ("O valor de a é: {}", a); a=6; println! ("O valor de a é: {}", a);
}

O programa acima é executado sem erros porque adicionamos mut à variável. No TypeScript, você pode alterar variáveis ​​do tipo let e var , conforme mostrado no exemplo abaixo:

//deixe
deixe a=5;
console.log (a);
a=6;
console.log (a); //var
var a=5;
console.log (a);
a=6;
console.log (a);

Rust possui a palavra-chave const . No entanto, você só pode definir o valor da variável no tempo de execução sozinho, não no tempo de compilação.

 fn outra_função (x: i32)-> i32 { return x + 1;
} fn main () { //ATRIBUIÇÃO DE TEMPO DE EXECUÇÃO, se você substituir const por let, não haverá erro de compilação const z=outra_função (5); println! ("O valor de z é: {}", z);//6
}

Como let pode ser definido em tempo de compilação e const não, o código gera um erro em tempo de compilação. Embora as variáveis ​​Rust sejam imutáveis ​​por padrão, você pode redefinir ou sombrear variáveis ​​do tipo let :

 fn main () { deixe a=5; seja a=a + 1; deixe a=a * 2; println! ("O valor de a é: {}", a);//12
}

No TypeScript, você só pode sombrear ou redefinir variáveis ​​do tipo var por padrão:

 var a=6; var a=a + 1; var a=a * 2; console.log (a);

Estruturas e objetos

Os objetos são uma parte muito importante do TypeScript. Você pode pensar em um objeto como uma pessoa que possui características diferentes, chamadas propriedades. Você deseja ser capaz de vincular e acessar essas características da pessoa como se estivessem vinculadas:

 var person={ nome:'Ugochi', país:'Nigéria', idade: 10 }
console.log (pessoa.nome);//Ugochi
console.log (pessoa.country);//Nigéria
console.log (person.age);//10

No entanto, no Rust, você não cria objetos. Você cria uma instância com um tipo struct . Estruturas são chamadas de structs no Rust. As estruturas colocam propriedades relacionadas em um tipo de dados.

Vamos recriar a amostra de objeto de nosso objeto TypeScript com uma estrutura:

 fn main () { deixe pessoa=Pessoa { nome: String:: from ("Ugochi"), país: String:: from ("Nigéria"), idade: i32:: de (10) }; println! ("{}", pessoa.nome);//Ugochi println! ("{}", pessoa.país);//Nigéria println! ("{}", person.age);//10
} struct Person { nome: String, país: String, idade: i32,
}

Você pode construir objetos a partir de outros objetos no TypeScript, conforme mostrado no exemplo abaixo:

 const Manager={ nome:"John", idade: 27, trabalho:"Engenheiro de Software"
}
const estagiário=Gerente;
console.log (intern.name);//John
console.log (intern.age);//27
console.log (intern.job);//Engenheiro de software

As estruturas também podem ser construídas a partir de outras estruturas:

 fn main () { deixe pessoa=Pessoa { nome: String:: from ("Ugochi"), país: String:: from ("Nigéria"), idade: i32:: de (10) }; println! ("{}", pessoa.nome);//Ugochi println! ("{}", pessoa.país);//Nigéria println! ("{}", person.age);//10 deixe estagiário=pessoa; println! ("{}", intern.name);//Ugochi println! ("{}", intern.country);//Nigéria println! ("{}", intern.age);//10
} struct Person { nome: String, país: String, idade: i32,
}

Propriedade

Um dos motivos pelos quais os desenvolvedores amam o Rust são seus recursos de gerenciamento de memória. No Rust, você não precisa se preocupar com como o coletor de lixo limpa a memória que não é mais necessária do heap. Tudo o que você precisa fazer é solicitar memória do alocador de memória. Rust retorna automaticamente essa memória para o alocador quando você terminar.

 fn main () { deixe mut s=String:: from ("Bem-vindo"); s.push_str (", Ugochi!");//push_str () anexa um literal a uma String println! ("{}", s);//Isso imprimirá `Bem-vindo, Ugochi!` }//Este escopo acabou e s não é mais válido.

No exemplo acima, solicitamos memória do alocador de memória. Como não sabemos o tamanho da string (ela pode ser alterada no programa mut ), ela é enviada para o heap em vez da pilha.

Imediatamente, o escopo acabou. Rust retorna a memória que s estava ocupando de volta para a pilha. Não precisamos escrever programas para limpar ou devolver a memória ao heap quando terminarmos de usá-los.

O TypeScript usa um coletor de lixo, que gerencia a memória do heap. Assim como no Rust, ele faz isso automaticamente.

Vejamos o exemplo abaixo do Mozilla :

 var y={ uma: { b: 2 }
};
//dois objetos são criados. Um é referenciado por ser atribuído à variável'y'
//O outro é referenciado pelo outro como uma de suas propriedades.
//Nenhum pode ser coletor de lixo. var y=x;//A variável'x'é a segunda coisa que faz referência a um dos objetos. y=1;//O objeto que estava originalmente em'y'tem uma referência exclusiva da variável'x'. var z=x.a;//Referência à propriedade'a'do objeto. //O objeto agora tem 2 referências: uma como uma propriedade, a outra como a variável'z'. x='mozilla';//O objeto que estava originalmente em'y'não tem nenhuma referência a ele. Pode ser coletado no lixo. //No entanto, sua propriedade'a'ainda é referenciada pela variável'z', portanto, não pode ser liberada. z=nulo;//A propriedade'a'do objeto originalmente em'y'não tem referências a ela. Pode ser coletado no lixo.

A memória não é liberada de volta para o heap no final do escopo porque uma propriedade da variável ainda está em uso.

Portabilidade

Portabilidade e estabilidade são dois motivos comuns para mudar de uma linguagem de programação para outra. Que tal uma linguagem que ofereça suporte a vários sistemas operacionais e arquitetura?

Rust permite a compilação cruzada. Possui compilações binárias dos compiladores para Windows, MacOS, Linux etc . Você pode usar compilações binárias de qualquer sistema operacional para construir para outra arquitetura.

Embora o TypeScript não ofereça tanta portabilidade quanto o Rust, ele pode ser executado em praticamente qualquer sistema operacional que você imaginar. Você não precisa instalar nenhum ambiente de tempo de execução para executar o código TypeScript.

Rust vs. TypeScript: por que você deve mudar

O motivo mais comum para mudar para Rust é sua segurança de memória garantida . Os desenvolvedores que escolhem o TypeScript geralmente valorizam sua intuitividade e segurança de tipo em JavaScript .

Mudar seu aplicativo de uma linguagem de programação para outra pode ser estressante e complicado. Você pode se perguntar se é melhor apenas deixar a pilha de aplicativos do jeito que está. Ninguém quer ser um retardatário em seu campo; Conforme os ecossistemas de front-end e de desenvolvimento da web evoluem, é fundamental encontrar seu lugar nele. Aprender a escrever os mesmos tipos de aplicativos e executar as mesmas funções em várias linguagens de programação pode ajudá-lo a se tornar um desenvolvedor mais completo.

Neste artigo, demonstramos como mudar de TypeScript para Rust e vice-versa. Também abordamos alguns desafios que você provavelmente encontrará ao fazer essa alteração e exploramos as muitas semelhanças entre TypeScript e Rust.

A postagem Mudando de Rust para TypeScript (e vice-versa) apareceu primeiro no LogRocket Blog .

Source link