TLPP x GO x Rust — Overview

Ricardo Mansano Godoi
TOTVS Developers
Published in
8 min readSep 9, 2020

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 1
varinfo(“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 Referencia
output:
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
return
output:
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.

--

--

Ricardo Mansano Godoi
TOTVS Developers

Chief engineer of Front-end and Development Tools on TOTVS, developing software since 88, plugged to the brand new technologies and Nerd to the bone.