TLPP x GO x Rust — Overview
O vídeo a seguir trata deste assunto:
https://www.youtube.com/watch?v=Jy9qImW-Zh0
Neste overview vou fazer uma comparação entre o TLPP, a linguagem oficial da TOTVS, o GOLang, da Google, e o Rust, da Mozilla, mostrando pontos parecidos entre as linguagens e onde cada uma delas se destaca.
O foco das três linguagens é segurança, desempenho e concorrência.
A concorrência em TLPP se reflete nos processos em background, que acontecem em nosso Servidor de Aplicação enquanto você está cadastrando seu cliente ou pedido, como Jobs, Rests e WebServices.
Vamos falar de outras características das linguagens.
Compilação
- TLPP é pré-compilado, onde otimizamos o código antes de seu envio ao repositório de programas, e interpretado, onde uma virtual machine executa esses programas
- O Go e o Rust são compilados.
Paradigmas
- As três linguagens são concorrentes, onde trabalham bem com multiprocessos
- As três são imperativas, onde temos uma sequência bem definida de passos
- As três são estruturadas, onde podemos criar sub-rotinas, como funções por exemplo
- O TLPP e o Rust são orientadas a objeto
- O Rust é funcional, onde a linguagem se baseia em um conjunto de funções matemáticas, que evitam dados mutáveis e estados.
Propósito
- O TLPP tem como propósito a manipulação e armazenamento de dados, pois é voltado para ERP’s
- O GO foca produtividade e simplicidade no back-end
- O Rust, segurança de memória e programação de baixo nível, como o C ou C++.
Garbage Collector
- O TLPP tem garbage, e também permite a criação de objetos em heap (New / FreeObj)
- O GO tem garbage, mas o controle é da aplicação
- O Rust não tem garbage, nem no stack nem no heap, ele organiza em tempo de compilação a criação e destruição das variáveis em memória, e faz isso muito bem, evitando memory leaks e double deletes, o que tem chamado a atenção da linguagem para aplicações críticas.
Medalha para o Rust, no que se refere a segurança de memória
Curiosidades
- O TLPP, leia-se AdvPL, nasceu da necessidade de manter milhões de linhas de código em Clipper da antiga Microsiga, e hoje é a linguagem oficial do ERP mais utilizado na América Latina
- Dada sua escalabilidade e performance no back-end, o GO se mostrou um substituto viável para o JavaScript e para o Python, e cresce de maneira estável em número de usuários desde 2016
- O Rust é considerado por muitos o primeiro substituto viável para o C e C++, podendo ser compilado em conjunto com o C, e a linguagem “mais amada” desde 2016, pelo guia da stack overflow, https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages
Dicas no header dos códigos fonte
TLPP: https://gist.github.com/ricardomansano/7055749679008fc37817d8e9a7c21ed2
GO: https://gist.github.com/ricardomansano/f80f4afa5eb8ed7c84b99fcb9505383a
Rust: https://gist.github.com/ricardomansano/bb736870e2ad09336f4e1b4321965b18
Vamos ver os códigos fonte
Tipagem
O TLPP tem tipagem dinâmica e fraca pois permite mudar o tipo depois de definido, e inferida por definir o tipo através da atribuição de valores.
Em futuras versões o TLPP terá a tipagem forte ligada por padrão.
private s as char; s := "Hello" // variavel tipada na declação
i := 42 // tipagem inferida
f := 999.01
conout (cValToChar(s)+" => "+valType(s))
conout (cValToChar(i)+" => "+valType(i))
conout (cValToChar(f)+" => "+valType(f))output:
Hello => C
42 => N
999.01 => N
GO têm tipagem estática, que não pode ser alterada depois de definida, inferida e forte.
var s string = "Hello"
i := 42
f := 999.01
fmt.Println(s + " => " + reflect.TypeOf(s).String())
fmt.Println(strconv.Itoa(i) + " => " + reflect.TypeOf(i).String())
fmt.Println(fmt.Sprintf("%f", f) + " => " + reflect.TypeOf(f).String())output:
Hello => string
42 => int
999.010000 => float64
O Rust também tem tipagem estática, inferida e forte.
Para o Rust toda declaração de variável é CONST, ou seja, não pode sofrer alteração no conteúdo depois da declaração. Para que uma variável tenha seu valor alterado é necessário usar a tag mut (mutable).
Medalha pro Rust, pois essa característica ajuda muito o compilador e na manutenção de um código eficiente.
let mut s:&str = "Hello";
let i = 42;
let f = 999.01;
println!("{} => {}", s, type_of(&s));
println!("{} => {}", i, type_of(&i));
println!("{} => {}", f, type_of(&f));fn type_of<T>(_: &T) -> &'static str {
std::any::type_name::<T>()
}output:
Hello => &str
42 => i32
999.01 => f64
Slice
O Slice é uma referência de tamanho dinâmico pra outra estrutura.
O TLPP não possui Slice, mas aqui um truque pra usar a referência à partir da função aCopy()
array := {{“d”},”a”,” “,”T”,”O”,”T”,”V”,”S”}
slice := array(5); aCopy(array,slice)
slice[1,1] := “@” // TLPP eh Base 1varinfo(“array”, array)
varinfo(“slice”, slice)
O GO utiliza um Slice a partir de um array, e após definir um Slice com as posições que desejo manipular, ao alterar a referência, automaticamente a variável origem é alterada.
Medalha pro GO, que não poderia ser mais intuitiva essa alteração por referência.
array := []string{“d”, “a”, “ “, “G”, “o”, “o”, “g”, “l”, “e”} // variável original
slice := array[0:5] // slice das 5 primeiras posições da variável array
slice[0] = “@” // Alterar a Referenciaoutput:
array: [@ a G o o g l e] => []string
slice: [@ a G o] => []string
O Rust possui Slice mas não permite a alteração do valor por referência, ou eu não soube fazer, caso algum dos colegas saiba, agradeceria se colocasse nos comentários
let array = “da Mozilla”; // @ &str eh um Slice
let slice = &array[0..5];output:
array: da Mozilla => &str
slice da Mo => &str
Arrays e Tuplas
Arrays são estruturas muito conhecidas por nós Dev’s, mas as tuplas nem tanto, elas são muito parecidas com structs, onde os tipos são inferidos por atribuição.
Para o TLPP um array atua como uma tupla, pois permite a inserção de tipos diferentes, e também permite a atribuição inferida e estática:
arr_dyn := {1,2,3} // inferida
arr_stat := array(3) // estatica
arr_stat[1]:=1;arr_stat[2]:=2;arr_stat[3]:=3
touple := {1,1.1,’a’,”abc”} // array atua como uma tupla
O GO não possui tuplas, e a declaração de arrays deve seguir o mesmo tipo de variáveis, caso seja necessário, as structs cumpririam o papel de tuplas no GO.
arrDyn := []int32{1, 2, 3} // inferida
arrStat := [3]int32{1, 2, 3} // estatica
O Rust atua de maneira idêntica ao GO na atribuição de arrays, porém o tipo tupla ajuda na simplificação de algumas operações.
let arr_dyn = [1,2,3]; // inferida
let arr_stat:[i32; 3] = [1,2,3]; // estatica
let touple = (1,1.1,’a’,”abc”); // tupla
Closures
Closures são basicamente funções anônimas, que podem ser passadas como parâmetros em funções, facilitando operações em diferentes escopos de execução.
Para o TLPP uma closure é um blóco-de-código, que permite sua execução a partir da função Eval().
closure := {|num| num*=3, num}
conout( “”,”Closure => “ + cValToChar(eval(closure, 10)) )
Para o GO a declaração da closure é muito simples, e sua execução pode ser feita instanciando a variável como se fosse uma função.
closure := func(num int) int {
num *= 3
return num
}
fmt.Printf(“\nClosure => %d\n”, closure(10))
A declaração da closure em Rust é um pouco diferente, porém a execução é idêntica.
let closure = |mut num:i32|{
num*=3;
num
};
println!(“\nClosure => {}”, closure(10));
Medalha pro TLPP, em nome do Clipper, que já usa o conceito de closure há 30 anos.
Classes
O conceito de classes é muito parecido nas três linguagens, expondo propriedades e métodos de maneira muito simples.
No TLPP a atribuição de métodos é feita associando os mesmos à classe mãe.
classe := myClass():myMethod(“Class created”)
FreeObj(classe) // Morre tbem qdo sai do escopo// Classe
class myClass
Public data myData
Public method myMethod(str as string)
endClass
method myMethod(str as string) class myClass
self:myData := str
conout(::myData)
return
O GO não se considera uma linguagem orientada a objeto, mas permite sua criação através de structs.
classe := myClass{}
classe.myMethod(“Class created”)// Classe
type myClass struct {
myData string
}
func (myself *myClass) myMethod(str string) {
myself.myData = str
fmt.Printf(“%s\n”, myself.myData)
}
O Rust tem na minha opinião o construtor mais intuitivo de classes, bastando declarar seus métodos através da tag impl (implementation).
Motivo que leva a medalha neste quesito.
let mut classe = MyClass {my_data: “”.to_string()};
classe.my_method(“Class created”.to_string());// Classe
struct MyClass {
my_data: String
}
impl MyClass{
fn my_method(&mut self, str: String){
self.my_data = str;
println!(“{}”, self.my_data);
}
}
Concorrência
A concorrência é muito importante em qualquer linguagem, permitindo múltiplos processos, acelerando os resultados.
No TLPP a função StartJob() cumpre este papel, executando um processo em paralelo até o seu fim.
StartJob(“threadTest”,getenvserver(),.F.,”Chamada concorrente”, 10)
threadTest(“Chamada direta…..”, 3)// Concorrencia
stat func threadTest(s, loops)
local i
for i := 1 to loops
conout(s + “ => “ + cValToChar(i))
sleep(100)
end
returnoutput:
Chamada concorrente => 1
Chamada direta..... => 1
Chamada concorrente => 2
Chamada direta..... => 2
Chamada concorrente => 3
Chamada concorrente => 4
Chamada direta..... => 3
Chamada concorrente => 5
Chamada concorrente => 6
Chamada concorrente => 7
Chamada concorrente => 8
Chamada concorrente => 9
Chamada concorrente => 10
No caso do GO e do Rust o processo em paralelo é terminado assim que a última linha da função original é executada, como podem ver no output da execução. Para mudar este comportamento é necessária a criação de semáforos, mas não falarei deste assunto neste documento.
No GO,criar um multi processo é muito simples, bastando colocar o comando go na frente da chamada da função.
go threadTest(“Chamada concorrente”, 10)
threadTest(“Chamada direta…..”, 3)// Concorrencia
func threadTest(s string, loops int) {
for i := 1; i <= loops; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s, “=>”, i)
}
}output:
Chamada direta..... => 1
Chamada concorrente => 1
Chamada concorrente => 2
Chamada direta..... => 2
Chamada direta..... => 3
No Rust o processo chama um spawn.
thread::spawn(|| {
thread_test(“Chamada concorrente”.to_string(), 11);
});
thread_test(“Chamada direta…..”.to_string(), 4);// Concorrencia
fn thread_test(s:String, loops: i32){
for i in 1..loops {
println!(“{} => {}”, s, i);
thread::sleep(Duration::from_millis(1));
}
}output:
Chamada direta..... => 1
Chamada concorrente => 1
Chamada concorrente => 2
Chamada direta..... => 2
Chamada concorrente => 3
Chamada direta..... => 3
Chamada concorrente => 4
Medalha pro TLPP, pois, para ele missão dada, é missão cumprida, executando o processo em paralelo até seu fim
Medalha pro GO pois não poderia ser mais simples iniciar um processo em paralelo
Espero que este documento ajude você, programador TLPP/AdvPL, a perder o medo e conhecer essas novas tecnologias.
E que usem essas linguagens onde elas mais se destacam, lembrando que num desenho bem feito de arquitetura, elas podem atuar em conjunto com nosso ERP em alguns processos específicos.
E a pergunta é recorrente, e tenho certeza que vocês estão se fazendo enquanto leem este documento:
Ah! A TOTVS um dia vai usar Rust ou GO por padrão?
Acredito que o Rust esteja mais “perto” de nossa arquitetura atual, baseada em C++, então na minha humilde opinião a resposta é “sim”, verificando a viabilidade, seria um forte aliado ao nosso produto.
Quanto ao GO, a pegada dele é diferente, mas acho que temos muito que aprender com sua eficácia no que se refere a escalabilidade, então todos nós estamos de olho.