tcxsproject.com.br livros... · primeiro gostaria de agradecer à minha família e aos meus pais,...

209

Upload: others

Post on 16-Oct-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

136777

Page 3: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos
Page 5: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

ImpressoePDF:978-85-94188-42-7

EPUB:978-85-94188-43-4

MOBI:978-85-94188-44-1

Casovocêdesejesubmeteralgumaerrataousugestão,acessehttp://erratas.casadocodigo.com.br.

ISBN

Page 6: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Primeiro gostaria de agradecer à minha família e aos meuspais,porestaremsemprecomigoemeapoiando.Obrigada,DiegoeKinjo,eobrigadaaospequenossemprealegresecontentesFluffy,Ffonxio,Highlandereaosminiffonxios.

Tenhodeagradeceràequipemaravilhosaquecolaboracomigona ThoughtWorks, especialmente Enzo, Icaro, Inajara, Roberto,Thait e Thais. Ainda na ThoughtWorks, agradeço ao pessoal demarketing,quefazmilagresaconteceremeespecialmenteaoPauloCaroli,porestarsempremeapoiando,eaoLucianoRamalho,queéumdosdesenvolvedoresmaisincríveisqueconheço.Alémdisso,deforadaThoughtWorks,queroagradeceràAnaPaulaMaia,pornossasconversassobreRust,eaoBrunoTavares,portambémserumentusiastadeRust.ÀMozilla,queapoiouacriaçãodoRustesoubeconduziralinguagemaummomentoincrível.

Queroagradecera todaa equipedaCasadoCódigo,quemeajudounodesenvolvimentodestelivro,especialmenteaVivianeaBianca.

Por último, agradeço a você, leitor, que dedicou um tempopara aprenderRust e agora quer procurar novas ideias para essalinguagemtãolegal.

AGRADECIMENTOS

Page 7: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Julia Naomi Boeira é desenvolvedora de software naThoughtWorksBrasil, liderandoodesenvolvimentodeRealidadeAumentadaesempreapoiandonovaslinguagensparatrazerumaculturapoliglota.Atuacomoobjetivodetrazermaisknow-howdelinguagens funcionais e permitir maior diversidade depensamentosnosprojetos.

Bruno Lara Tavares acompanha o desenvolvimento de Rustdesde2015einteressa-seemcomointegraressenovoecossistemaem projetos atuais. Já trabalhou em diversas linguagens comoconsultore,hojeemdia,desenvolvemicrosserviçosemClojure.

SOBREAAUTORA

Sobreorevisortécnico

Page 8: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Este livro apresenta Rust de forma que a programaçãofuncionalsejainerenteaodesenvolvimento,assimcomoidentificaumadasgrandesforçasdoRust,aconcorrência.Paraisso,olivrofoidivididoemquatropartes.

Na primeira, temos um resumo do potencial do Rust e suahistória.PassaremosporumaexplicaçãodeporqueRustdeveserconsiderado uma ótima opção, bem como por suas principaiscaracterísticas,eapresentaremosumbrevemódulodecomopodeser feito TDD em Rust. Na segunda,mostramos a programaçãofuncionalnaperspectivadessalinguagem,comparandoaoClojure.Nosso foco será principalmente em funções, traits, iterators,adapterseconsumers.

Na terceira parte, apresentamos a concorrência nos diversosmodos que o Rust oferece, como a criação de threads, ocompartilhamentodeestadoseatransferênciadeinformaçõesporcanais. Então, na última parte, resumimos as outras, de forma aapresentar 4 frameworksHTTP, sendodois de alto nível (Iron eNickel), um de baixo nível (Hyper) e um de programaçãoassíncrona(Tokio).

Este livro tem como objetivo usar Rust para desenvolver opensamentofuncionaleconcorrenteemqualquerpessoaquegostede programação, por conta dos motivos já explicados. Logo, érecomendado terumbásico conhecimentonessa linguagem,pois

INTRODUÇÃO

Público-alvo

Page 9: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

muitasdasimplementaçõesdecódigoesperamumconhecimentorasodele.

OutrosaspectosimportantesdaescolhadeRustforamapaixãodaautorapela linguagem, jáquepoupoumuitasdoresdecabeçapor não ter mais de se preocupar em implementar sistemasconcorrentes em C++, e a facilidade da sintaxe, que tem umasensaçãodelinguagemdealtonível.

Page 10: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

1

2

8

22

30

31

Sumário

PorqueRust?

1IntroduçãoaoRust

1.1HistóriadoRust 4

2PorqueRust?

2.1TypeSafety 102.2Entendimentodalinguagemesuasintaxe 122.3Segurançadememória 142.4Programaçãoconcorrente 182.5MaissobreRust 20

3TDDemRust

3.1PorqueTDD? 223.2UmexemploemRust 25

Programaçãofuncional

4Oqueéprogramaçãofuncional?

SumárioCasadoCódigo

Page 11: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

38

48

58

62

74

75

4.1Imutabilidade 344.2Laziness 354.3Funções 36

5Definindofunções

5.1Funçõesdeordemsuperior 415.2Funçõesanônimas 425.3Funçõescomovaloresderetorno 45

6Traits

6.1Traitbounds 506.2Traitsemtiposgenéricos 516.3Tópicosespeciaisemtraits 52

7Ifletewhilelet

7.1iflet 597.2ifletelse 607.3whilelet 61

8Iterators,adapterseconsumers

8.1Iterators 648.2Maps,filters,foldsetudomais 678.3Consumer 71

Programaçãoconcorrente

9Oqueéprogramaçãoconcorrente?

9.1Definiçãodeconcorrência 759.2PorqueClojureéumaboareferênciadecomparação? 76

CasadoCódigoSumário

Page 12: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

82

90

95

102

103

129

136

9.3EoRust?Comofica? 779.4Rust:concorrênciasemmedo 799.5Quandoutilizarconcorrência? 81

10Threads—Abasedaprogramaçãoconcorrente

10.1Lançandomuitasthreads 8410.2Panic!attheThread 8710.3Threadsseguras 88

11Estadoscompartilhados

12Comunicaçãoentrethreads

12.1Criandochannels 9612.2Enviandoerecebendodados 9712.3Comofunciona? 9812.4Comunicaçãoassíncronaesíncrona 99

Aplicandonossosconhecimentos

13Aplicandonossosconhecimentos

13.1Iron 10413.2MaisdetalhesdoIron 11213.3Irontestes 117

14BrincandocomNickel

14.1Routing 13114.2LidandocomJSON 13214.3TemplatesemNickel 133

15Hyper:servidoresdesenvolvidosnomaisbaixonível

SumárioCasadoCódigo

Page 13: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

177

196

15.1CriandoumservidorHelloWorldmaisrobusto 13815.2FazendonossoservidorresponderumPost 14015.3UmaAPIGraphQLcomHypereJuniper 145

16Tokioeaassincronicidade

16.1Tokio-protoeTokio-core 17816.2Futures 18716.3Aversãoassíncrona 190

17Bibliografia

Versão:22.7.20

CasadoCódigoSumário

Page 14: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

IntroduçãoaoRustPorqueRust?TDDemRust

PorqueRust?

Page 15: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO1

Figura1.1:LogodalinguagemRust

Rustéumalinguagemdesistema,comoCeC++,comalgumascaracterísticas que a tornammuito diferenciada, alémde possuirumacomunidadebastanteativaparaumalinguagemtãojovemeopensource.Algumasdessascaracterísticasmarcantessão:

Abstrações semcusto, pois é fácil abstrair comtraits,especialmentecomousodegenéricos.Semânticademove(veremosbastantenaParte3).Segurançadememóriagarantida.Trabalhar commúltiplas threads é fácil devido ao fatodeter segurança de memória, auxiliada pelo compilador nadetecçãodedataraces.

INTRODUÇÃOAORUST

2 1INTRODUÇÃOAORUST

Page 16: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Programação orientada a dados é simples, especialmenteporterpatternmatching,imutabilidadeporpadrãoeoutrascaracterísticasfuncionais.Inferênciadetipo.Macros: regras e correspondência de padrões que separecemfunções,masqueaumentammuitoaversatilidadedoquepodemosfazercomnossasfunções.Fácil geração de executáveis binários com alta integraçãocom diferentes sistemas, com um sistema de execuçãomínimo e bindings eficientes para diversas linguagens,especialmenteCePython.

O objetivo do Rust é sermemory safe (segurança a nível dememória)efearlessconcorrency(concorrênciasemmedo).Ouseja,o acessodedadosnãovai causar efeitos inesperados,permitindodesenvolveraplicaçõesconcorrentessemsepreocuparcomacessoa informações inexistentes, ou acesso à mesma informação depontosdiferentes.

Além disso, a linguagem ganhou o prêmio de "most lovedprogramming language" do Stack Overflow Developer Survey, em2016e2017.AlgunsprincípiosqueservemcomobaseparaoRusteseusreflexosjáforamcomentados:segurançasemcoletordelixo,concorrênciasemcorridadedados,abstraçãosemoverheadeumcompiladorquegarantesegurançanoalocamentoderecursos.Nãosepreocupe,aolongodolivro,especialmentenopróximocapítulo,vamosverestascaracterísticasemdetalhes.

AlgoqueconsideromuitoimportanteparaocontextodolivroéexplicaromotivodomeuinteresseemRust.Soudesenvolvedorade jogos, principalmente C++ e C#, e de microsserviços,

1INTRODUÇÃOAORUST 3

Page 17: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

principalmente em Clojure. Porém, desenvolver jogosperformáticosemC/C++comaltocontroledememóriararamenteé seguro, e C# dificilmente é performático por conta própria,mesmoquesejabastanteseguro.

JánoaspectodoClojure,temosumalinguagemextremamentefuncionalcomumasintaxe limpageradapelasintaxeLisp,eumaforma eficiente de trabalhar commacros e concorrência. Assim,sempre foi um grande desejo e umanecessidade uma linguagemsegura,poucoverbosaecomaspectofuncional,aníveldesistema.

Para começar nossa trajetória e justificar nossa escolha porRust,vamosàsuahistória.

A linguagem Rust surgiu de um projeto pessoal iniciado em2006pelofuncionáriodaMozilla,GraydonHoare.Segundoele,oprojeto foi nomeado em homenagem à família de fungos daferrugem (rust, em inglês). Em 2009, a Mozilla começou apatrociná-loeanunciou-oem2010.

Inicialmente, o compilador escrito por Hoare era feito emOCaml,porém,em2010,elepassouaserauto-hospedadoeescritoem Rust. O compilador é conhecido como rustc, e utiliza comoback-end a LLVM (Low Level Virtual Machine, ou MáquinaVirtual de Baixo Nível), tendo compilado com sucesso pelaprimeiravezem2011(KAIHLAVIRTA,2017).

A primeira versão pré-alfa do compilador Rust ocorreu emjaneirode2012.Jáaprimeiraversãoestável,Rust1.0,foi lançadaem15demaiode2015.Apósisso,tomou-sehábitolançarupdates

1.1HISTÓRIADORUST

4 1.1HISTÓRIADORUST

Page 18: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

acadaseissemanas.Fala-sequeosrecursossãodesenvolvidosnaferrugemnoturna(NightlyRust), sendotestadasemversõesbetasqueduram6semanas.

O estilo do sistema de objetosmudou consideravelmente emsuas versões 0.2, 0.3 e 0.4, pois a 0.2 introduziu os conceitos declassespelaprimeiravez.Ecomaversão0.3, foiadicionadaumasériederecursos,incluindodestrutoresepolimorfismospormeiodo uso básico de interfaces, bem como todas as funcionalidadesligadasaclasses.

EmRust0.4,ostraits foramadicionadoscomoummeioparafornecer herança. As interfaces foram unificadas com eles ecolocadascomoumafuncionalidadeseparada.Asclassestambémforam removidas, substituídas por uma combinação deimplementaçõesetiposestruturados.

Começando em 0.9 e terminando em 0.11, oRust tinha doistipos de ponteiros embutidos: ~ (til) e @ (arroba). Parasimplificar o modelo de memória, a linguagem reimplementouesses tipos de ponteiros na biblioteca padrão com o Box. Porúltimo,foiremovidoogarbagecollector(KAIHLAVIRTA,2017).

Em janeiro de 2014, o editor-chefe do Dr. Dobb comentousobreaschancesdeRustsetornarumconcorrenteparaC++eC,bemcomoparaasoutraslinguagenspróximas,comoD,GoeNim(entãoNimrod):"deacordocomBinstock,enquantoRustera'umalinguagem extraordinariamente elegante', sua adoção pelacomunidadepermaneceuatrasada,porqueelacontinuavamudandocontinuamenteentreasversões".

Sobreasconstantesmudanças

1.1HISTÓRIADORUST 5

Page 19: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Rust começou com o objetivo de criar uma linguagem deprogramação de sistemas segura. Em busca deste objetivo,explorou muitas ideias, algumas das quais mantiveram vidas outraços, enquanto outras foram descartadas (como o sistematypestate,ogreenthreading).

Alémdisso,nodesenvolvimentopré-1.0,umagrandepartedabibliotecapadrão foi reescrita diversas vezes, já queosprimeirosdesenhosforamatualizadosparamelhorusarosrecursosdaRustefornecerAPIs de plataforma cruzada de qualidade e consistente.AgoraqueRust atingiu1.0,o idiomaégarantidocomo "estável";emborapossacontinuaraevoluir,ocódigoquefuncionanoRustatualdevecontinuarafuncionaremlançamentosfuturos.

Paraconcluir,Rustmereceatençãoporserumalinguagemdealtaperformance,comnúmeroreduzidodebugsecaracterísticasagradáveis de outras linguagens modernas em uma comunidademuito receptiva. Uma demonstração da preocupação dareceptividadedacomunidadeéaseguintefrasesobreosmateriaisparaaprendizadodeRustteremumalcancemelhor:

6 1.1HISTÓRIADORUST

Page 20: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

"Alguns grupos que estão sub-representados nas áreas detecnologia gostaríamos especialmente de incluir nacomunidade Rust, como mulheres (cis & trans), pessoas nãobinárias, pessoas de cor, falantes de outros idiomas além doinglês, pessoas que aprenderam programação mais tarde navida(maisvelhosouapenasnafaculdade,ouemumbootcampcomo parte de uma mudança de carreira da meia-idade),pessoascomdeficiênciaoupessoasquetêmdiferentesestilosdeaprendizagem"(NICHOLS,2017).

Nopróximocapítulo,entenderemosumpoucomelhorporqueRustéumaótimaescolhaecomopodemosenxergarseusatributosdesegurançaaníveldememória,concorrênciasemmedo,sintaxesimplesetipagemsegura.

Rust–https://www.rust-lang.org/en-US/OpenSource–https://github.com/rust-lang/rustFAQs–https://www.rust-lang.org/en-US/faq.htmlLLVM–https://en.wikipedia.org/wiki/LLVMTheHistoryofRust–https://youtu.be/79PSagCD_AY

Parasabermais

1.1HISTÓRIADORUST 7

Page 21: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO2

Este capítulo trata bastante de questões técnicas de baixonível. Caso você não tenha interesse neste momento, é umcapítuloquepodeserlidoaofinaldolivro.

ComoexplicadoporBlandy,emseulivroWhyRust?, existempelomenos4bonsmotivosparaseutilizaressalinguagem:

1. Tipagem segura (TypeSafety) – Isso quer dizer que Rusttem a preocupação de que sejam prevenidoscomportamentos de erro de tipagem, causados peladiscrepânciadoscomportamentosesperadosparacadatipo.Namaioriadaslinguagens,issoficaacargodoprogramadorou,emmuitoscasos,a linguagemédinâmica,masemRustissoéumafuncionalidadedelaprópria.

2. Entendimento da linguagem e sua sintaxe – Rust possuiuma sintaxe inspirada em outras linguagens, por isso ela ébastantefamiliar.

3. Uso seguro da memória – Linguagens como C/C++possuem muito controle sobre o uso de memória eperformance, sem segurança. Já linguagens como Java, que

PORQUERUST?

8 2PORQUERUST?

Page 22: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

utilizam coletores de lixo, praticam a segurança do uso dememória,mas sem controle dememória e performance.ORustprocuraconciliaramboscomousodapropriedadedereferências,ownership.

4. Programação concorrente – Rust implementa soluções deconcorrênciaqueimpeçamdataraces.

A execução de um programa contém data races se estepossuirduasaçõesconflitantesem,pelomenos,duasthreadsdiferentes.

Há algum tempo, aproximadamente duas gerações delinguagensatrás,começamosaprocurarsoluçõesmaissimplesemlinguagensdealtonível–queevoluíamdeformamuitorápida–,para resolver tarefas que antes eram delegadas a linguagens deníveldesistema.Felizmente,mesmonaslinguagensdebaixonível,houve uma pequena evolução. Além disso, alguns problemasexistentesnãosãofacilmenteresolvidosdeformatãoeficienteemlinguagensdealtonível.Algunsmotivossão:

Édifícil escrever código seguro.É comumexploraçõesdesegurança se aproveitarem da forma como o C e o C++lidam com amemória. Assim, Rust apresenta uma baixaquantidadedebugsemtempodecompilaçãoporcausadasverificaçõesdocompilador.É ainda mais difícil escrever código concorrente dequalidade, que realmente seja capaz de explorar ashabilidades das máquinas modernas. Cada nova geração

2PORQUERUST? 9

Page 23: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

nos traz mais cores, e cabe às linguagens conseguiremexplorarissodamelhorformapossível.

ÉnestecenárioqueRustaparece,poissupreosdefeitosqueaslinguagensanteriorestêmdificuldadedeevoluir.

É triste que as principais linguagens de nível de sistema nãosejamtypesafe(CeC++)enquantoamaiorpartedaslinguagenspopulares é, especialmente considerando que C e C++ sãofundamentaisnaimplementaçãobásicadesistemas.Sendoassim,tipagem segura parece-me algo bastante óbvio para se ter emlinguagensdesistema.

Esse paradoxo de décadas é algo que o Rust propõe-se aresolver: ao mesmo tempo que é uma linguagem de sistema,também é type safe. Conseguimos um design que permiteperformance,controlerefinadoderecursose,aindaassim,garanteasegurançadetipos.

Tipagemsegurapodepareceralgosimplesepoucopromissor,mas essa questão torna-se surpreendente quando se analisa asconsequências de programação concorrente. Além disso,concorrênciaéumproblemabastantecomplexodeseresolveremC e C++, causando muita dor de cabeça. Desse modo,programadores focam em concorrência somente quando esta éabsolutamente a única opção. Mas a tipagem segura e aimutabilidadepadrãodoRust tornamcódigo concorrente,muitomaissimples,aliviandograndepartedosproblemasdedataraces.

2.1TYPESAFETY

10 2.1TYPESAFETY

Page 24: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

DATARACES

Data races ocorrem quando, em um único processo, pelomenosduasthreadsacessamamesmareferênciadememóriaconcorrentemente, epelomenosumadelas édeescrita semque locks (bloqueios) sejam aplicados pela thread parabloquearamemória.

Rust garante comportamentos muito seguros devido à suatipagem estática, porém checagens de segurança sãoconservadoras, especialmente se o compilador tem de seencarregar disso. Para isso, é preciso fazer comque as regras docompiladorfiquemmaisflexíveis.

Akeywordunsafenospermiteisso.UmexemplodefunçõesquedevemsermarcadascomounsafesãoasdeFFI(InterfacesdeFunçõesExternas).Existem3grandesvantagensdeutilizaressecódigo:

1. Acessareatualizarumavariávelestáticaemutável;2. Diferenciarponteirosbrutos;3. Chamarfunçõesinseguras.

Um exemplo para acessar e atualizar valores é na forma derealizaroperaçõesinsegurasquepossamlevaraproblemas,comotransformar valores do tipo &str em &[u8]

(std::mem::transmute::<T,U>(t:T)).

Safeeunsafe

2.1TYPESAFETY 11

Page 25: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

fnmain(){letu:&[u8]=&[10,20,30];

unsafe{assert!(u==std::mem::transmute::<&str,&[u8]>("123"));}}

Alógicaa seguirpoderesultaremponteirosvazios.Paranãotermosisto,precisamosdiferenciarponteirosbrutos.

fnmain(){letponteiro_bruto:*constu32=&10;

unsafe{assert!(*ponteiro_bruto==10);}}

Algo importante no desenvolvimento do Rust foi evitar umasintaxeinovadora,portanto,váriaspartesdasintaxeserãobastantefamiliar.Considereasfunçõesaseguir:

fnmain(){println!("{:?}",1+1);}

Esta funçãoapresentauma funçãoprincipal,main, tambémconhecida como thread principal, que executa um simplescomandodeimprimirnateladoconsoleoresultadode1+1.

fnfiltra_multiplos_de_seis(quantity:i32)->Vec<i32>{(1i32..).filter(|&x|x%2i32==0i32).filter(|&x|x%3i32==0i32).take(quantityasusize)

2.2ENTENDIMENTODALINGUAGEMESUASINTAXE

12 2.2ENTENDIMENTODALINGUAGEMESUASINTAXE

Page 26: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

.collect::<Vec<i32>>()}

A keyword fn introduz a definição de uma função,enquantoosímbolo->apósalistadeargumentosdefineo tipo de retorno. Muitas linguagens de programaçãoutilizam termos como fun, def e fn para definirfunções e tipos de retorno especificados ao final dadeclaraçãodafunção,comoo:(doispontos)emScala.Variáveissãoimutáveisporpadrão,assim,akeywordmutdeve ser adicionada às variáveis para que elas possam serresignadas.Ovalori32 representa um valor inteiro de 32 bit comsinal. Outras opções são o u32, que seria um valor deinteirode32bits sem sinal.Outros tipospossíveis são i8,u8,i16,u16,i32,u32,i64,u64,f32ef64.A expressão (1i32..) representa uma sequência devaloresquevariade1i32atéinfinito.As filter() , take() e collect() representamfunções para iterar sobre a sequência, filtrando númerosdivisíveis por 2 e por 3, tomando os 5 primeiros destasequência e transformando-os em um vetor do tipo i32,respectivamente.Rust possui a keyword return , porém o compiladorinterpretaoúltimovalor sem; (ponto e vírgula) comovalorderetorno.

Na função a seguir, podemos ver a ausência de valores deretorno:

fnimprime_numero_se_chute_correto(chute:i32){letnumero=3i32;assert_eq!(chute==numero);

2.2ENTENDIMENTODALINGUAGEMESUASINTAXE 13

Page 27: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

println!("Onumero{}estácorreto!",chute);}

Akeywordlet introduzvariáveis locais, eoRust inferetiposdentrodefunções.Existem duas macros presentes nesta função:assert_eq!, que verifica se a condição é válida e, casonão seja, o programa é interrompido comumpanic; eprintln!,queimprimeovalorchutenolugardo{}.

AgoraprecisamosverocoraçãodasreivindicaçõesdoRusteomotivo pelo qual ele alega ter concorrência segura. Essacaracterísticaédefinidapor3princípios-chave:

1. Semacessoavaloresnulos,ouseja,semdereferênciadenullpointers. Seuprogramanão vai ter um crash se você tentarumadereferênciaaumnullpointer.

2. Todos os valores estão associados a um contexto, ou seja,sem ponteiros soltos. Cada variável vai ter seu lifetime(tempo de vida) pelo tempo que for necessário viver.Portanto, seuprogramanuncautilizarávalores alocadosnaheapdepoisdeelatersidoliberada.

3. Sembufferoverruns.Seuprogramanuncaacessaráelementosalémdofinalouantesdoiníciodeumarray.

Nullpointeréquandoexisteumponteiroparaumareferêncianula(vazia)namemóriaheap.Umaformabastantesimplesdenãopermitirousodenullpointerénuncadeixá-losseremcriados.

2.3SEGURANÇADEMEMÓRIA

Dereferênciasdenullpointer

14 2.3SEGURANÇADEMEMÓRIA

Page 28: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

EmRust,nãoháoequivalentedonulldoJava,nullptrdoC++ouoNULLdoC.Alémdisso,Rustnãoconverteinteirosparaponteiro, logo,não épossível usar inteiros como se faz emC++.Portanto, essa linguagem não permite a introdução de nullpointers.

Paraquesejapossívelutilizaroconceitodevalorvazio,queémuito útil, e não ter os problemas causados por null pointers, oRustusaemsua implementaçãopadrãooenumOptional<T>(https://rustbyexample.com/std/option.html).Umvalordestetiponãoéumponteiro,e tentardiferenciaréum typeerror. SomentequandoverificarmosqualvariaçãodeOptional<T>tivemos,pormeiodadeclaraçãomatch,poderemosencontraroSome(ptr)eextrairoponteiroprt,queagorapossuigarantiadesernãonulo.

ProgramasRustnuncatentamacessarvaloresalocadosnaheapapós terem sido liberados. Isso não é algo incomum, todas aslinguagens com garbage collectors (coletores de lixo, ou GC)garantem isso, em maior ou menor grau. O que o Rust faz dediferenteéfazerissosemGCesemcontagemdereferências.

Pointeirossoltos

2.3SEGURANÇADEMEMÓRIA 15

Page 29: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

MEMÓRIASTACKEHEAP

A stack (ou pilha) é muito rápida e é onde a memória éalocada por padrão no Rust. Mas a alocação em stack élimitada à chamada de uma função, pois possui tamanhobastante limitado. A heap, por outro lado, é mais lenta eprecisa ser explicitamente alocada. Ela tem tamanhoefetivamente ilimitado, mas aloca blocos de memória deformaarbitrária.

GARBAGECOLLECTOR

GC é um processo para a automação do gerenciamento dememóriapeloprograma.Comele, épossível recuperarumaárea de memória sem ponteiros apontando para ela. Issoajuda a evitar problemas de vazamento de memória,resultandonoesgotamentodamemórialivreparaalocação.

O GC foi inventado em 1959 por John McCarthy pararesolverproblemasdememóriaemLisp.Umalinguagemqueopta por ter coletor de lixo é uma que escolhe possuir umamaioremaiscomplexaoperaçãoderuntimequeoRust.Rustéusadodiretonohardware,semruntimeextra,ecoletoresdelixo são usualmente a causa de comportamentos nãodeterminísticos.

16 2.3SEGURANÇADEMEMÓRIA

Page 30: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Para evitar esses problemas, Rust possui 3 regras paradeterminarquandoumvaloréliberado,egarantirquenãoexistamais nenhum ponteiro quando se atinjam esses pontos. Outrofator interessante é que essas regras são resolvidas em tempo decompilação.

Regra1:todos os valores possuemumúnicodono (owner)emumdadomomento.Umvalorpodesermovido(move)de um owner para outro, porém, quando esse ownerdesaparecer,ovalorseráliberado.Regra2:vocêpodepegaremprestada(borrow)areferênciaaumvalor, desde que elanão sobrevivamais que o dono.Referências emprestadas são ponteiros temporários,permitindooperarsobrevaloresquevocênãopossui.Regra 3: você só pode alterar um valor quando possuiracessoexclusivoaele.

Vale fazer um adendo sobre a cópia de tipos específicos emRust, pois existem duas categorias. O primeiro caso é quandopodemoscopiarbitabit,semtratamentoextra.IssopodeserfeitopormeiodeimplementaçõesespeciaisdotraitCopy.

Utilizar cópias permite que a referência mantenha seu donosemqueacópiasofracomafunçãodroppelomeiodocaminho.Atraitcopynãoédesignadaautomaticamenteparatiposnovospelaanotação#[derive(Copy,Clone)] ouimplCloneforTipo.Osegundocasoéparatodososoutrostipos,quedevemsermovidospordesignaçãoenãopodemsercopiados.

Umbufferoverrunacontecequandoumsoftwareexcedeouso

Bufferoverrun

2.3SEGURANÇADEMEMÓRIA 17

Page 31: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

de memória resignado a ele pelo sistema operacional, passandoentão a escrever no setor de memória contíguo. Em outraslinguagens de nível de sistema, como C e C++, não se indexaefetivamente arrays,mas simponteiros, que contêm informaçõesdo início e fim do array, ou de quaisquer outros objetosrelacionados.

Isso faz com que os programadores sejam responsáveis porverificar as fronteiras do array, e eles usualmente cometempequenoserrosnessesentido.EmRust,nãoseindexamponteiros,massimarrayseslices,quepossuemfronteirasbemdefinidas.IssogarantequeataquescomoovírusMorris (primeirovírusdo tipoverme distribuído pela internet, que explorava buffer overruns)não aconteçam e que as pessoas não possam explorar falhas debuffers.

Após termos explicado type safety e ownership, podemosapresentarograndediferencialdoRust:programaçãoconcorrentesemdatarace.Issotemoobjetivodepermitirqueaconcorrênciasejaoprimeirorecursodoseuprograma,enãooúltimo.

Para um entendimento claro, a concorrência ocorre quandoexistemdoisoumaisprocessosexecutadosemsimultâneo,porém,logicamenteindependentes.

Aprimeiraetapaantesdasincronizaçãodethreadsévercomocriá-las.Esse tópico seráexploradomaisa fundoduranteo livro,

2.4PROGRAMAÇÃOCONCORRENTE

Criaçãodethreads

18 2.4PROGRAMAÇÃOCONCORRENTE

Page 32: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

mas agora vamos fazer uma apresentação básica. Para isso,utilizamos a função std::thread::spawn com uma closure(maissobrefunçõeseclosuresnaParte2)deargumento.ImagineumathreadqueretornaopoderdeGokuapósamedidadeki:

letthread_poder=std::thread::spawn(||{println!("Medindooki");return10000;});

Essa thread imprime uma string e encerra retornando umJoinHandle , um valor que espera o método join() paraencerrá-la.Assim,paramedir seoki émaisde8000, faríamososeguinte:

letthread_poder=std::thread::spawn(||{println!("Medindooki");return10000;});assert!(try!(thread_poder.join())>=8000);

OqueaconteceriaseoGokutentassefazerumkaioken10xaomesmotempoqueumkaioken20x?

letmutpoder_goku=1;letkaioken_10x=std::thread::spawn(||{poder_goku*=10});letkaioken_20x=std::thread::spawn(||{poder_goku*=20});

O compilador Rust proibe com o seguinte erro: error:closure may outlive the current function, but it

borrows'x',whichisownedbythecurrentfunction.

ComonossaclosureutilizaopoderdeGokudeseuambienteexterno, Rust trata a closure como uma estrutura de dados quepegou emprestado (borrow) a referência mutável parapoder_goku. O erro acontece, pois o Rust não consegue tercerteza sobre a qual função poder_goku pertence neste

2.4PROGRAMAÇÃOCONCORRENTE 19

Page 33: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

momento, assim o compilador impede que estas duas threadsocorram.

Umasoluçãoseriausarstd::thread::scoped,quecriaumJoinGuard emvezdeumaJoinHandle.Adiferença é que aJoinGuard tranca a threadatéque ela termine, impedindoqueeladuremaisquesuaJoinGuardpermite.

Como entraremos em mais detalhes sobre concorrência nofuturo, vale passar aqui apenas um pequeno resumo de uma desuas maiores vantagens, os Mutex. Quando múltiplas threadsdevem ler ou modificar dados compartilhados, elas precisam decuidados redobradospara garantir sincronia entre elas. Portanto,umamaneiradeprotegerestruturasdedadoséutilizarMutex.

Somente uma thread poderá prender (lock) amutex por vez.Assim, a thread acessará a estrutura somente quando trancar amutexpelo lock.Os passos de lock eunlock que a thread realizaservempara sincronizar a operação, impedindo comportamentosindefinidos.

Apesardeserumalinguagemnova,elaébastanterobusta,temfeatures importantes e uma comunidade muito ativa em seudesenvolvimento,queveremosumpoucoadiantenolivro.Vejaaseguiralgumascaracterísticasimportantes:

Bibliotecascompletasdecoleçõescomsets,maps,vectors,sequênciaseassimpordiante.Abibliotecapadrão jávemcom implementações muito boas de coleções, o quepermite um ótimo desenvolvimento no nível de

2.5MAISSOBRERUST

20 2.5MAISSOBRERUST

Page 34: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

programaçãofuncionaleestruturadedados.Apoio ao desenvolvimento de macros, como asapresentadasanteriormenteprintln!() eassert!().O Lisp e o Clojure são linguagens que permitem muitaflexibilidade e abstração com macros poderosas, já asmacros do Rust estão muito mais próximas da liberdadequeoLispnosdádoqueoC++nospermite.Ótimo sistema de módulos. Cargo, o gerenciador dedependências, apoia muito o desenvolvimento de libs,utilização de crates e sistemas de módulos isolados quepodemserconsumidosdeformapontual.Gerenciador de crates (dependências) cargo. As crates deRustlocalizam-senocrates.io,eocargoauxiliaemseugerenciamentoatravésdocargo.toml e na compilação,execuçãoetestesdesuasbibliotecaseprogramas.

AgoraquejásabemosporqueusarRust,nopróximocapítulovamosparaumexemplodecomoaplicarTDDnessalinguagem.

Ownership –https://github.com/bltavares/presentations/blob/gh-pages/rust-tipos-e-ownership/rust-tipos-e-ownership.orgGenéricos – https://doc.rust-lang.org/book/first-edition/generics.htmlEnums – https://doc.rust-lang.org/book/first-edition/enums.htmlPanic – https://doc.rust-lang.org/book/second-edition/ch09-01-unrecoverable-errors-with-panic.html

Parasabermais

2.5MAISSOBRERUST 21

Page 35: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO3

Antes de entrar em detalhes sobre TDD em Rust, querocontar uma história sobre isso. Não faz muito tempo quepostei emuma comunidadeRust umquestionamento sobrepor que existem tão poucas bibliotecas com testes emRust,mesmoalinguagempossuindoumsuportenativoparaisso.

Outracoisaquecomenteifoiafaltadecratesparamelhoraraexperiênciadetestes.Arespostamesurpreendeudetalformaque tive de deletar o comentário de tão pasma que fiquei:"Como Rust é uma linguagem segura, não precisamos detestesparagarantirqualidadeesegurança".

Tendo feito esse pequeno disclaimer, vamos ao motivo deusarmosTDD.

TDD(Test-DrivenDevelopment,oudesenvolvimentoguiadoatestes) é um ciclo de desenvolvimento de software que parte doprincípio "teste primeiro, code depois". Mas é claro que não se

TDDEMRUST

3.1PORQUETDD?

22 3TDDEMRUST

Page 36: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

trata só disso, e a figura a seguir é um exemplo do ciclo dedesenvolvimentodesoftwareporTDD.

NãoéfunçãodestelivroensinarsobreTDD,mascreioquesejaimportante gerar a mentalidade de que, mesmo para linguagensseguras,testessãoimportantes,jáqueRustnãolhepermitecausarproblemassemquerer,maspermitecausarproblemasporquerer.

Então, voltando à pergunta, por que TDD? Porque ele nospermite quebrar o loop de feedback negativo ou consecutivo, epossibilita manter um custo de desenvolvimento constante comalgunsganhos.

Figura3.1:CiclodoTDD

No livro The Rust Programming Language, encontramos aseguinte citação de Edsger W. Dijkstra, em The HumbleProgrammer(1972):

3.1PORQUETDD? 23

Page 37: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Teste de programas pode ser uma maneira muito eficienteparaencontrarbugs,masdesesperadamenteinadequadaparamostrarafaltadeles.

Esta frase é bastante antiga, e omindset de testes já evoluiumuito.O que a frase representa paramim é o fato de que testesgarantemquebugsnãoacontecerãoquando seus "casosde teste"estiveremcobertos.

Entretanto,éimpossíveldeterminarseonossocódigonãotembugsmesmosendotestado,poisnãopodemospreveraexistênciadebugspara todosos casos.Por isso, práticas comooTDDnosajudam a reduzir bugs, especialmente os semânticos, pois ossintáticosnossãomostradospelocompilador,jáqueodesigneodesenvolvimentodocódigoficamrestritosaostestes.

Você pode ver mais informações no livro desenvolvido pelaequipe do Rust, em: https://doc.rust-lang.org/book/second-edition/ch11-00-testing.html.

Bomcritériodeaceitação: é fácil entender testesever seestão validando os casos previstos. O desenvolvimentoguiadoatestesgarantequetodonovocódigoprecisapassaremtodosostestes,oquenospermiteescrevercódigonovosemquebraroprograma.Foco:comojásabemosquaistestesdevemosimplementar,o risco de perder a atenção diminui consideravelmente.

Algunspossíveisbenefícios

24 3.1PORQUETDD?

Page 38: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Fica fácilpreverquaispassosdevemossatisfazer, evitandoquetentemosimplementarcoisasdesnecessárias.Refatorar: TDD garante muita segurança na hora derefatorar. A refatoração pode mudar o estilo do código,aplicando simplificações,mas não pode quebrar testes ouadicionarlógicasemteste.Menosbugs:aindahádiscussõessobreaveracidadedessefato, mas, pela minha experiência, parece ser verdade.Também é uma referência ao argumento apresentadoanteriormente.Documentação atualizada: cada nova implementaçãoexigirá novos testes, por isso é fácil verificar o quequeremosqueo software faça.Testes representamoatualestado do código, tornando obsoleta a necessidade deescrever centenas de linhas de documentos para acompreensãodocódigo.

EstelivropressupõequevocêjáconheçaobásicodeRust,porisso vou concentrar as informaçõesnaparte relacionada a testes.Uma forma de começar a pensar em Rust e TDD é utilizar ocomando cargo new <nome da lib> --lib, pois gera umtemplatedelibquecomeçapelostestes.

Nosso problema será bem simples: encontrar os divisores deumnúmero inteiromaiorque zero.Assim, eledeve terum testequegarantaumpanic!()casoonúmerofor0.Quandocriamosuma lib em Rust, nosso arquivo src/lib.rs inicia jápreparadopararecebertestes:

3.2UMEXEMPLOEMRUST

3.2UMEXEMPLOEMRUST 25

Page 39: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

#[cfg(test)]modtests{#[test]fnit_works(){}}

EmRust, costumamos criarummódulode testes,para testesunitários,eumapastade testes,chamadatests, para testes deintegração. Neste caso, vamos nos preocupar somente com osunitários,devidoàsimplicidadedoproblema.

Aanotação#[cfg(test)] representaqueestemódulodeveser compilado somente para testes, e a anotação #[test]

representaqueafunçãoaseguiréumtesteaserrodado.OstestesemRustfuncionamdaseguintemaneira:

Se tudo ocorrer bem, o teste passa, como a funçãoit_works()nosmostra.Sehouverumpanic!,otestefalha.Masequandoqueremostestarumpanic!?Utilizamosaanotação#[should_panic].

Nossoprimeirotesteseráoseguinte:

#[cfg(test)]modtests{usesuper::*;

#[test]#[should_panic]fnzero_is_not_valid(){divisores(0);}}

Primeiroprecisamosrodarocomandocargotest,quefaráum build e verificará se os testes passam. Para passar este teste,

26 3.2UMEXEMPLOEMRUST

Page 40: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

adicionamosafunçãoaseguir.Elaretornaummontedeavisosdemauusogeradospelocompiladoremrelaçãoàvariávelvalor,inclusiveavisandoquaisfunçõesquenuncaforamutilizadas:

fndivisores(valor:i64){panic!("0nãoéumvalorválido");}

Agora, com o teste, queremos testar outro valor menor quezero:

#[test]#[should_panic]fnnegativos_nao_sao_validos(){divisores(-10);}

Felizmente,elepassa,masamensagemestáestranha.Umaboapossibilidade émudar para{} <= 0 não é valido, valor.Também vale a pena adicionar a anotação #

[allow(dead_code)] no método divisores , pois assim ocompiladorentenderáqueéumadecisãoconsciente.

Agoravamosadicionarumtestequefazocompiladorfalhar:

#[test]fndois_divide_dois(){assert_eq!(divisores(2),vec![2])}

A mensagem de erro é a seguinte: expected (), found

structstd::vec::Vec. Para corrigir isso, precisamos garantirqueafunçãodivisoresretorneumVec<i64>.

#[allow(dead_code)]fndivisores(valor:i64)->Vec<i64>{ifvalor<=0{panic!("{}<=0nãoévalido",valor);}else{

3.2UMEXEMPLOEMRUST 27

Page 41: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

vec![2]}}

Agora percebemos que não estamos retornando 1, por issoadicionaremosumtestequegarantepanic!quandoforpassado1comoargumento.Faremosissopormotivosdesimplicidadeenãocomplexidadedateoriadosnúmeros.

#[test]#[should_panic]fnum_nao_eh_valido(){divisores(1);}...#[allow(dead_code)]fndivisores(valor:i64)->Vec<i64>{ifvalor<=1{panic!("{}<=0nãoévalido",valor);}else{vec![2]}}

Agoraprecisamosgarantirqueosdivisoresde3sejamapenas[3].

#[test]fntres_divide_tres(){assert_eq!(divisores(3),vec![3])}

Nosso vetor de retorno é [2], mas esperávamos [3]. Oseguintecódigonospermiteresolveresseproblema:

fndivisores(valor:i64)->Vec<i64>{ifvalor<=1{panic!("{}<=0nãoévalido",valor);}else{vec![valor]}}

28 3.2UMEXEMPLOEMRUST

Page 42: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Equantoao4?

#[test]fndivisores_de_quatro(){assert_eq!(divisores(4),vec![2,4])}

Estetestefalhaporretornarapenas[4],masasoluçãoparaesteproblemanãoécomplexa:

#[allow(dead_code)]fndivisores(valor:i64)->Vec<i64>{ifvalor<=1{panic!("{}<=0nãoévalido",valor);}else{(2..valor+1).filter(|x|valor%x==0).collect::<Vec<i64>>()}}

Agoravamostestarumacoleçãomaiscomplexa:

#[test]fndivisores_de_vintequatro(){assert_eq!(divisores(24),vec![2,3,4,6,8,12,24])}

Nossocódigoaindaéválido.Percebaqueasoluçãodotesteébastantefuncional:(2..valor+1).filter(|x|valor%x==0).collect::<Vec<i64>>() . Isso nos leva à próxima parte:programaçãofuncionalemRust.

3.2UMEXEMPLOEMRUST 29

Page 43: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Oqueéprogramaçãofuncional?DefinindofunçõesTraitsifletewhileletIterators,adapterseconsumers

Programaçãofuncional

Page 44: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO4

VejaumapproachbaseadoemClojure.

DISCLAIMER

1. Rust não é uma linguagem do paradigma funcional,masseaproveitademuitosaspectosdele.

2. Escolhi comparar com Clojure por ser a linguagemfuncional que mais tenho domínio e teoria parasuportarminhasideias.

Pessoalmenteesteéumdostópicoscomqualmaismedivirto:"a sagradadefiniçãode funcional."Hádesdepessoas falandoqueJava8éfuncionalatéoutrasprocurandodefiniçõesclarasdoqueéprogramação funcional. Para mim, uma das referências maisimportantesfoinolivroTheJoyofClojure,noqualaprimeirafrasesobreprogramaçãofuncionaléaseguinte:

OQUEÉPROGRAMAÇÃOFUNCIONAL?

Definiçãodeprogramaçãofuncional

4OQUEÉPROGRAMAÇÃOFUNCIONAL? 31

Page 45: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Rápido,oqueéprogramaçãofuncional?Respostaerrada.

A parte relevante desta pequena frase de impacto é queninguém sabe o que é programação funcional, e não há umadefiniçãomuito clara a respeito disso. É um daqueles termos dacomputação que tem uma definição amorfa. Cada pessoa vai tersuadefiniçãoe todasserãodiferentes,maso interessanteseráveralgumascaracterísticascomuns.

Felizmente,cadapessoavaipuxarmaisparaa linguagemqueprefere, como Haskell, ML, Racket, Shen e Clojure. O pontointeressanteéque,nogeral, funções serãoa característicacentraldas definições de programação funcional, sendo elas cidadãs deprimeiraclassenoparadigma.

Paratanto,vouintroduziradefiniçãomaispróximaaoClojurede funcional: pureza (funções puras), imutabilidade, recursão,laziness(algocomopreguiça,falandoespecialmentedesequências)etransparênciadereferências.

Rustnãotemfunçõespuraseneméofocodalinguagem,masas variáveis são imutáveis por padrão. É possível trabalhar comsequências de forma lazy, e o forte do Rust é o modo como asreferênciassão tratadas.Quantoàrecursão,confessoquenãoéofortedalinguagemepoucousei,masexistemduassoluções:

1. Definir uma função inline (que é uma definição de umaoperação fatorial) e chamá-la internamente, aplicando onovo padrão na própria chamada –muito parecido com o

32 4OQUEÉPROGRAMAÇÃOFUNCIONAL?

Page 46: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

que podemos fazer em Clojure. Podemos ver no código aseguir que a função fatorial está sendo usada em suaprópriadefinição.

fnmain(){fnfatorial(valor:u32)->u32{ifvalor==0{1}else{valor*fatorial(valor-1)}}

println!("{}",fatorial(5));}

1. Definimosclosuresdentrodestructsepassamosastructparaa clojure, assim como a função anterior, mas podemosutilizar nossa struct fatorial para receber uma funçãoanônima que opera sobre ela mesma. Na função a seguir,definimos uma struct que possui uma função comoargumento. Podemos ver que esta recebe elamesma e umtipo u32 como argumentos, retornando um novo u32, f:&'sFn(&Fatorial,u32)->u32.Quando essa struct édefinida emumlet, vemos que ela recebe o valor desseleteumqueéretornado.

fnmain(){structFatorial<'s>{f:&'sFn(&Fatorial,u32)->u32}letfact=Fatorial{f:&|fact,valor|ifvalor==0{1}else{valor*(fact.f)(fact,valor-1)}};

println!("{}",(fact.f)(&fact,5));}

Particularmente,achoaprimeirasoluçãomuitomaiselegantee "funcional", pois ela émais simples e resolve o problema commenosboilerplate.

4OQUEÉPROGRAMAÇÃOFUNCIONAL? 33

Page 47: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Boilerplate refere-se ao excesso de código que muitaslinguagensprecisamparaconseguirfazertarefasmínimas,ouà verbosidade necessária para atingir um estado. EmOrientação aObjetos (OO), é bemcomumchamargetters esettersdeboilerplate.

Assim como Clojure, Rust provê imutabilidade como pilarcentraldalinguagem,provendoassociaçõesimutáveisporpadrão.É preciso explicitar que a variável é mutável, com let mut

var_mutavel=3;,parapoderalterarseuvalor.Porém,paraqueserveaimutabilidade?

1. Invariante: provê garantias dos futuros estados do valor,impedindoquemuitosestadosindesejáveisaconteçam.

2. Refletirsobreprecauções:otrabalhodepensaremcomoavariávelvaiestareseuspossíveisestadosfuturosnãoprecisaser resolvido nem pelo compilador, nem pela pessoa quedesenvolve.

3. Igualdade:nãofazsentidofalardeigualdadedevariáveisseelas são mutáveis, já que, se hoje elas são diferentes,provavelmentesempreserão–emesmoquesejamiguais,émuito provável que no futuro deixem de ser. Quandofalamos de imutabilidade, podemos garantir que, se doisobjetossãoiguaishoje,elessempreserãoiguais.

4. Compartilhar:nocasodeprogramaçãoconcorrente,temosagarantiadequeessevalornãoserámutadopela thread,e

4.1IMUTABILIDADE

34 4.1IMUTABILIDADE

Page 48: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

seu valor poderá ser usado para outros fins com maisliberdade.

Clojureéumalinguagemparcialmente lazynamaneiracomolida com suas sequências.Mas o que isso realmente significa?Aseparaçãoentrelazyeeager(impaciente)ébastantesimples:

Lazy deixa para avaliar os argumentos somente quandonecessário.Eageravaliaosargumentosassimqueelessãopassados.

Temos o Haskell como exemplo de uma linguagemextremamente lazy.Mas linguagenseagerstambémpossuemseusmomentos lazy, e isso não as transforma em lazy. Um exemplodissoéooperador&&doJava.

No código a seguir, temosumif comduas condições quedevem ser verdadeiras: a primeira verifica se o objeto existe, e asegundaverificaseométodoretornatrue.Veja:

if(obj!=null&&obj.isWorking()){...}

O Javadeixapara avaliar o resultadoda expressão&& apósvalidar se obj != null, ou seja, um comportamento lazy. OmesmoaconteceemClojurecomakeywordand:

(defngrande-and[xyz](andxyz(do(println"tudoverdade"):verdade)))

(grande-and()100true);"Tudoverdade"

4.2LAZINESS

4.2LAZINESS 35

Page 49: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

;=>:verdade

(grande-andtruefalsetrue);=>false

A função clojure definida, em defn como grande-and ,recebe 3 argumentox,y,z. E caso todos eles sejamtrue,imprime"Tudoverdade"eretornaakeyword:verdade.

Podemosverque,noprimeirousodegrande-and, todososcasosforamavaliadoscomoverdade,portanto,oandfoiavaliadocomo verdade. Já o segundo caso retornou false , pois aavaliação do segundo item já retornou falso, interrompendo asequênciadevalidação.Rusttambémpossuisuasequêncialazy,oRange&lt;I&gt;,escrito(inicio..fim).

O cernedaprogramação funcional é baseada emum sistemacomputacional conhecido como lambda calculus. Em Clojure,assim como em Rust, funções são cidadãs de primeira classe epodemserpassadascomoargumentoecomovalorderetorno.

Além disso, Rust possui todas as atribuições relacionadas aclosure e traits. O pecado de Rust nesse sentido são as funçõespuras.Javalidacomfunçõesdeformadiferente,emquetudodeveser resolvido em objetos, forçando todas as modelagens decomportamento a serem instâncias de classes. Em oposição aoparadigmadoJava,Clojuretratafunçõescomodados,permitindoque4itensimportantesestejamfortementepresentesemRust.Asfunçõespodemser:

Criadassobdemanda;

4.3FUNÇÕES

36 4.3FUNÇÕES

Page 50: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Armazenadasemestruturasdedados;Passadascomoargumentoparafunções;Retornadascomovaloresdefunções.

AgorapodemosentendermelhorcomoRustlidacomfunções,pois,comomencionadoanteriormente,elas sãooaspectocentraldaprogramaçãofuncionaleserãovistascommaiorprofundidadenopróximocapítulo.

4.3FUNÇÕES 37

Page 51: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO5

AprimeirafunçãoquevocêveráemRustéafnmain(),queprovavelmente foigeradaquandovocêrodouocomandocargonewmeuapp--bin, ao brincar com a linguagem. E ela virá naseguinteforma:

fnmain(){println!("Hello,world!");}

EssaéafunçãocentraldaoperaçãodeumprojetoemRust,eequivale ao main em outras linguagens como Clojure, Java,C/C++, C#. Funções são definidas pela palavra reservada fn ,seguidaspelonomedafunção,depreferênciaemsnake_case –casovocêerreospadrõesdeestilo,ocargoavisará–,eparênteses() (ainda muito semelhante a muitas linguagens). Após isso,virãoaschaves{...}comocorpodafunção.

NotequeoRust temuma sintaxemuitoparecida comváriaslinguagens.Oobjetivodissofoitorná-lomaisfácildeaprender.

No capítuloTDD em Rust, já apresentamos algumas macroscomo assert_eq! e panic! , porém temos mais trêsinteressantesparafalar:oprint!eoprintln!,quesãomacrosque imprimem valores no console, e o assert!, que funcionamuito como o assert_eq. A diferença é que este recebe dois

DEFININDOFUNÇÕES

38 5DEFININDOFUNÇÕES

Page 52: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

valoresecompara-os,enquantooassert!recebeumaexpressãológicacomoassert!(expected>=actual).

Nestelivro,vamosutilizarasprincipaismacros.Porém,sevocêdeseja ver mais possibilidades, o livro The Rust ProgrammingLanguagecontémumcapítulobastantesólidosobremacros.Paramais, acesse: https://doc.rust-lang.org/book/first-edition/macros.html.

Agora queremos que nossa função receba um valor inteiro.Rust possui algumas características interessantes a respeito deinteiros e floats, como seumodo de definir o tipo específico deinteiros e floats.Nossa função outra_func deverá receber umvalorinteirodotipoi32eimprimi-lonatela:

fnoutra_func(x:i32){println!("{}",x);}

Podemosverqueoargumentorecebidopelafunçãofoixdotipo i32 . Com dois argumentos, nossa assinatura da funçãoficaria assim:fnoutra_func(x:i32,y:u64){.No corpodas funções, deparamo-nos com duas situações: declarações eexpressões.

Declaraçõessãoinstruçõesquerealizamalgumaação,masnãoretornamvalores.Criarumavariáveléumadeclaraçãolet x = "declaracao". Declarações duplas não sãopermitidas,poiselasnãoretornamvalor,comoemletx=(letz="Naopermitido").

Expressões avaliam para retornar valores. Expressões sãocomoageradaaseguir,naqualoblocoavaliapararetornar2:

5DEFININDOFUNÇÕES 39

Page 53: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

{letx=1;x+1}

Agoradigamosquea funçãopi() retornaum floatdo tipof32.Paraisso,podemosescreveressafunçãodessaforma:

fnpi()->f32{3.1415f32}

fnmain(){letpi=pi();}

Osímbolo->defineotipodevalorderetornoe,casoelenãoesteja presente, a função não tem valor de retorno. Neste caso,estamos retornando um f32 . A falta de ; depois do3.1415f32é intencional,poisestaéaformacomaqualoRustretornavalores(aúltimalinhaquenãopossui;eéumvalor).

Felizmente, há outras formas de retornar valores em Rust,comoousodapalavrareturn,evocêveráemalgunsprogramasapalavrarettambém.Vejaoexemploseguinte:

fnpos(x:i32)->i32{ifx>0{returnx;}else{1}}

Retornar valores com o return no fim da função éconsideradoumpéssimoestilo,eocompiladorreclamará:

fnpi()->f32{return3.1415f32;}

40 5DEFININDOFUNÇÕES

Page 54: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Um último tópico geral sobre funções são atribuições devariáveisqueapontamparafunções.Digamosquetemosafunçãoinc(x:i32)->i32;elaincrementaovalordex,umi32,eretornaumnovovalori32:

fninc(x:i32)->i32{x+1}

Existem duas maneiras de atribuirmos essa função a umavariável.Aprimeiradelasécominferência,letf=inc;, e asegunda é sem inferência, let f: fn(i32) -> i32 = inc;.Agorapodemoschamarafunçãoflivremente:

fninc(x:i32)->i32{x+1}

letf=inc;letdois=f(2);

Creio que este seja o primeiro passo para considerar funçõesemRust comocidadãsdeprimeira classe.Opróximoé entendercomoelasutilizamoutrasfunções.

Como já atribuímos uma função a um parâmetro, agoraprecisamos poder fazer algo com isso. Quem sabe passar essafunção comoparâmetro?Énessahoraque entramas funçõesdeordemsuperior:

fnordem_superior<F>(valor:i32,func:F)->i32whereF:Fn(i32)->i32{func(valor)}

5.1FUNÇÕESDEORDEMSUPERIOR

5.1FUNÇÕESDEORDEMSUPERIOR 41

Page 55: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Essencialmente, isto é uma função de ordem superior,mas aparteinteressanteéotipodosegundoparâmetro,func.Féumtipo genérico definido dentro da cláusula where . A cláusulawhereéusadapara fazera ligaçãoentreos tiposutilizadoseostiposgenéricos.

Nonossoexemplo,a ligaçãode tipodoF é o tipoFN, ouseja, um trait para o tipo função, que nesse caso recebe umargumentodotipoi32eretornaoutroargumentodotipoi32.

Lembradanossafunçãoinc()?ElaéumadotipoFN,querecebe um i32 como argumento e retorna um i32. Sendoassim, podemos aplicá-la na nossa função ordem_superior .Vamosprimeirocriarumtesteparaverificaraveracidadedisso:

#[test]fnfunn_ordem_superior(){assert_eq!(onze,ordem_superior(10i32,inc))}

Agoraquevalidamosoteste,podemosaplicaroconhecimento:

letonze=ordem_superior(10i32,inc);

Utilizamos nosso conhecimento com sucesso, mas parecenecessário gerar funções anônimas – ou, como chamamos emRust,closures–parapodermospassá-lascomoargumentoparaaordem_superior. É significativamente comum passar funçõesanônimasparaoutrasfunções,masocasoquemaisvejoépassarclosureparafunçõesdastd::iter:Iterator,comoomap(),filter(),fold(),scan().

5.2FUNÇÕESANÔNIMAS

42 5.2FUNÇÕESANÔNIMAS

Page 56: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Funções anônimas (ou closures) são um grupo especial defunçõesdoRustedemuitasoutras linguagens,quenãopossuemumadefinição estática, sendogeradas inline.Umaclosurepareceassim:

letinc=|x:i32|x+1;

assert_eq!(2,inc(1));

Maisespecificamente,aparte|x:i32|x+1 representaaclosurequeadiciona1aovalorxdotipoi32.Osargumentospassadosparaaclosurevãoentre||,eépossívelterclosurescommaisdeumalinhautilizando{}.

letinc_duplo=|x:i32|{letmutresultado=x;

resultado+=1i32;resultado+=1i32;

resultado};

assert_eq!(3,inc_duplo(1));

Umaquestãointeressanteaseobservaréqueotipodoretornonãoestáexplícitonaclosure,epoderianãoestarexplícitonotipodoargumento.Issoocorrepois,enquantoéútilsaberostiposdasfunçõesdeclaradas–jáqueauxiliacomdocumentaçãoeinferênciadetipos–,asclosuresnãosãodocumentadasporseremanônimas,logo,nãocausamostiposdeerrosquefunçõesdeclaradascausam.Uma comparação interessante que pode ser feita em relação adeclaraçõesdeclosureséaseguinte:

fnmultiplicar_pi_1(x:f64)->f64{x*PI}letmultiplicar_pi_2=|x:f64|->f64{x*PI};letmultiplicar_pi_2=|x:f64|x*PI;letmultiplicar_pi_2=|x|x*PI;

5.2FUNÇÕESANÔNIMAS 43

Page 57: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

A primeira linha é uma declaração de função convencional,querecebeumargumentodotipof64eretornaumf64,queéo argumentomultiplicado porPI. As três linhas seguintes sãofunções anônimas na qual o grau de tipagem das variáveis foireduzido, sendo a primeira a mais tipada e a segunda, a menostipada.

Além disso, closures possuem algumas característicasinteressantes: elas podem se relacionar com o ambiente na qualestão,podendousarconceitosdeborrow(emprestar).Alistagemaseguirmostraumaclosureacessandoumavariávelexternaaela:

letnum=10i16;letinc_num=|x:i16|x+num;

assert_eq!(20i16,inc_num(10i16));

Uma keywordmuito comum emRust é a move, que servepara garantir que a closure copie a ownership de seu ambienteexternoparadentrodela.Masporqueutilizamosomove?

Imagine o seguinte cenário na listagem anterior: num éimutávele foipassadocomoreferência imutávelparainc_num.Setentarmoscriarumanovareferênciamutável,ocompiladorvaigerar um erro reclamando que a variável já foi movida por umborrow (empréstimo) imutávelenãopermitiráborrowsmutáveis.Para resolver este tipo de situação, utilizamos o move , quepermite aonum implementarCopy e faz com que inc_numrecebaumacópiadenum.

Agorapodemoscontinuarnossalinhaderaciocíniodefunçõesdeordemsuperior,retornandofunçõesdefunções.

Move

44 5.2FUNÇÕESANÔNIMAS

Page 58: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Para tornar nossas funções cidadãs de primeira classe,precisamoscompletarociclodeusodelas.Jásabemoscomopassarfunções como argumento, como atribuir funções a variáveis ecomo declarar funções anônimas. Agora falta apenas aprender aretorná-las.

Confessoqueasoluçãodissoéumpoucoconfusainicialmente,mas bem simples de entender com ummínimo esforço. Em umprimeiromomento,édese imaginarque,para indicarqueotipoderetornoéumafunção,umapossívelsintaxeseriaapenascomatraitFn,comooexemploaseguir:

fnfinc()->(Fn(i32)->i32){|x|x+1}

Mas,infelizmente,asoluçãoéumpoucomaisverbosaeprecisafazerusodatraitBox<>comFn:

fnfinc()->Box<Fn(i32)->i32>{Box::new(|x|x+1)}

Box é um ponteiro inteligente (smart pointer), que permitevocêcolocarumvalornaheap.Podemoscolocarumvalori32nela,comomostraalistagemaseguir:

fnmain(){letb=Box::new(5);println!("b={}",b);}

5.3FUNÇÕESCOMOVALORESDERETORNO

Box

5.3FUNÇÕESCOMOVALORESDERETORNO 45

Page 59: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Issoimprimirá"b=5"natela.Nestecaso,podemosverqueébastantesimplesacessarvaloresdedadosdentrodeumbox,eémuito semelhante a como faríamos comvalores em stack.Assimcomo qualquer outro valor que possui ownership de dados, boxtambémsaideescopo,comobquandoamain()termina.

AdeslocaçãoaconteceparatodasaspartesassociadasaoBox.Colocarumúnicovalornaheapnãoparecemuitoútil,mas,comojá vimos anteriormente, os box permitem que coloquemosimplementaçõesdatraitFnnaheap.

Já falamosbastantesobre traits,masé importantevê-lasmaisprofundamente.Faremosissonopróximocapítulo.

Entretanto,esetivéssemosalgumavariávelnafunção?

fnfinc_num()->Box<Fn(i32)->i32>{letnum=5;Box::new(|x|x+num)}

Nosdepararíamoscomoseguinteerro:

error:closuremayoutlivethecurrentfunction,butitborrows`num`,whichisownedbythecurrentfunction[E0373]Box::new(|x|x+num)^~~~~~~~~~~

Esse problema ocorre porque nossa closure está pegandoemprestadonum, que se encontra no stack, portanto, possui omesmo tempode vida (lifetime) queo stack frame. Para resolverisso,podemosutilizaromove:

fnfinc_num()->Box<Fn(i32)->i32>{

RetornandofunçõescomoBox

46 5.3FUNÇÕESCOMOVALORESDERETORNO

Page 60: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

letnum=5;Box::new(move|x|x+num)}

Outraquestãoimportantedelembraréque,sealgumavariávelvinda de argumentos é passada para a função de resposta, éimportantedeclararoseulifetime:

fnretorno_ordem_superior<'a>(passo:&'ai32)->Box<Fn(i32)->i32+'a>{Box::new(move|x:i32|x+passo)}

Estecapítulotevecomoobjetivoapresentaraflexibilidadedasfunções Rust e utilizá-las em um contexto de programaçãofuncional. Um dos conceitos importantes a se usar aqui é evitarfunções que retornem void, pois isso quebraria um fluxo detransformaçãodedados,por exemplo,queveremosmelhormaisadiante.

The Rust Programming Language – https://doc.rust-lang.org/book/

Parasabermais

5.3FUNÇÕESCOMOVALORESDERETORNO 47

Page 61: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO6

TraitséumadasjoiasdeRust,eumdospilaresquepermiteaprogramação funcional nessa linguagem. Traits podem sertraduzidas por características. É graças a elas que podemosgeneralizarboxeimplementarnovasfunçõescomvelhosnomes.

UmatraitpermitegrandepotencialdeabstraçãoemRust.Elapossui somente uma descrição dos métodos, ou seja, suadeclaraçãodetiposouassinaturaenome,semimplementaçãoreal.Emumjogo,porexemplo,teríamosatraitInimigoquepossuiafunçãoatacar,algoparecidocom:

traitInimigo{fnatacar(&self);}

Agorapensequetemos3inimigos:oAzul,oVermelhoeoVerde. Para implementar a trait no inimigo Azul, faríamosassim:

implInimigoforAzul{

}

Issoresultaemumerroquesugerequenemtodososmétodosda trait foramimplementados.Pararesolveresseproblema,bastaimplementarométodoatacar:

TRAITS

48 6TRAITS

Page 62: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

implInimigoforAzul{fnatacar(&self){println!("Ataquerealizadocomdanode{:?}",self.dano);}}

Podemos ver que, para implementarmos uma trait para umtipo,utilizamosasintaxe:implNomeDaTraitforTipo{listade funções} . O GitHub(https://github.com/GodiStudios/mathf/blob/master/src/vector2.rs) possui algumas implementações interessantes para vetores ef32.Comoexemplodeumatraitmaiscompleta,poderíamosteros seguintes métodos para serem implementados quando ausarmos:

traitInimigo{fnnova(vida:u32,dano:u32)->self;fnatacar(&self);fnmovimentar(&self,distancia:i32);}

Asfunçõesqueaparecememtraitssãochamadasdemétodos,e omotivo disso é a presença do&self como argumento.Ouseja, o objeto que as invocou é um parâmetro. Além disso, umatrait pode provermétodos com uma implementação padrão. Dojeito que temos falado aqui, parece que traits são exclusivas destructs, mas elas podem ser implementadas em qualquer tipo, equalquertipopodeimplementarmuitasdelas.

Existe um grupo de traits bastante particular: aquelas quepodemserimplementadaspelaanotação#[derive(...)],comoa#[derive(Debug, Copy)]. São traits padrões para todos osnovos tipos, eutilizar anotaçõesparadefinir suas implementçõesfoiumaformasimplesderesolverumproblemacomum.

6TRAITS 49

Page 63: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Traitboundsseriamas"promessasdecomportamentodecadatrait".Issopermitequeasfunçõesgenéricasexploremostiposqueelas aceitam para implementar. Considere a seguinte função quenãocompila:

fninstancia<T>(inimigo:T){println!("Oinimigofoiinstanciadonaposicao{}",inimigo.spawn());}

PrecisamosgarantirqueotipoassociadoaoinimigopossaserSpawnable(istoé,utilizarafunçãospawn).Issopodeserfeitocomonoexemploaseguir:

fninstancia<T:Spawnable>(inimigo:T){println!("Oinimigofoiinstanciadonaposicao{}",inimigo.spawn());}

A sintaxe <T: Spawnable> significa "qualquer tipo queimplementeatraitSpawnable".Comoastraitsdefinemassinaturasdetipodefunções,podemostercertezadequequalquertipoqueimplemente Spawnable tenha um método .spawn() . Umexemplodecomoficariaissoseria:

traitSpawnable{fnspawn(&self)->Point2D;}

structInimigo{altura:i64,peso:i64,forca:f64,}

implSpawnableforInimigo{fnspawn(&self)->f64{

6.1TRAITBOUNDS

50 6.1TRAITBOUNDS

Page 64: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Point2D::new()//exemplofictício}}

fninstancia<T:Spawnable>(inimigo:T){println!("Oinimigofoiinstanciadonaposicao{}",inimigo.spawn());}

Structs genéricas podem se beneficiar de trait bounds, poispermitem garantir segurança na hora da implementação de umatrait.Parafazerisso,bastaadicionara"promessa"quandodeclararotipodoparâmetro.

Para o tipo elipse, podemos implementar um método queverifica se ela é na verdade uma esfera. Para isso, criamos umastruct com os parâmetros correspondentes ao que se espera deuma elipse e, no método eh_esfera , aproveitamos da traitPartialEqparagarantiraigualdadedotipoTcorrespondenteaaeab.

NossocódigodeclaraumastructElipse quepossui doisvalorescorrespondentesaoraio,a:Teb:T,que,paraseremumaesfera,devemteromesmovalor.Paraquepossamosverificarse a e b são iguais, precisamos garantir que o tipo TimplementeatraitPartialEq:

structElipse<T>{x:T,y:T,a:T,b:T,}

6.2TRAITSEMTIPOSGENÉRICOS

6.2TRAITSEMTIPOSGENÉRICOS 51

Page 65: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

impl<T:PartialEq>Elipse<T>{fneh_esfera(&self)->bool{self.a==self.b}}

Podemos perceber que o método eh_esfera() precisaaveriguarquea==b,ouseja,checaraigualdadeentreoslados.Para isso, é preciso que o tipo T implemente a traitcore::cmp::PartialEq. Isso permite que uma elipse, ou umretângulo, seja definida em termos de um tipo que implementeessatraitdeigualdade.

Vejaalgumasobservaçõessobreimplementaçõesdetraits:

Umatraitnãopodeserusadaedefinidadentrodomesmoescopo. Portanto, para usar uma, é necessário utilizar apalavra use, que nos permite incorporar estruturas dedadosefunçõesdeoutrosmódulos.

Aoutraregraébastantelógica:aimplementaçãodatraitoudo tipo deve estar no mesmo arquivo, ou seja,impl+trait||impl+type.

É possível ter uma trait com mais de uma "promessa"(bounds), basta utilizar o símbolo + para somar asbounds, por exemplo, fn foo<T: Clone + Debug>(x:T).Afunçãodessatraitprecisadedoisbounds,CloneeDebug.

Métodospadrãopodemserimplementadosdiretamentenatrait, caso se apliquem a todos os casos. A trait a seguir

6.3TÓPICOSESPECIAISEMTRAITS

52 6.3TÓPICOSESPECIAISEMTRAITS

Page 66: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

demonstraesseconceito:

traitFoo{fneh_valido(&self)->bool;

fnnao_eh_valido(&self)->bool{!self.eh_valido()}}

É possível ter bounds diferentes para tipos diferentes,conformealistagemreferenciadaaseguir.Infelizmente,ascoisaspodemficarmeiocomplexas, epode sernecessárioutilizar a cláusula where em uma das duas maneirasapresentadas.

//Primeirapossibilidadefnfoo<T:Clone,K:PartialEq+Debug>(x:T,y:K);

//Clausulawheresimplesfnfoo<T,K>(x:T,y:K)whereT:Clone,K:PartialEq+Debug;

//Clausulawheresegundaopçãofnfoo<T,K>(x:T,y:K)whereT:Clone,K:PartialEq+Debug;

Algumas traits repetitivas podem ser implementadas deforma mais simples, valendo-se da anotação #

[derive(...)].A lista é pequena,masmuito poderosa:... = Clone, Copy, Debug, Default, Eq, Hash,

Ord,PartialEq,PartialOrd.

Tipos associados são uma arma poderosa naimplementação de traits. Eles são uma forma de associaruma reserva de tipo dentro de traits, especialmente naassinatura de métodos. A implementação da trait vaiespecificarotipoespecíficoqueseráusadoemconjunto.

6.3TÓPICOSESPECIAISEMTRAITS 53

Page 67: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

///exemploretiradodohttps://doc.rust-lang.org/book/second-editiontraitIterator{typeItem;fnnext(&mutself)->Option<Self::Item>;}

AtraitIteratoranteriordefineafunçãonext paraotipo Item . Assim, o resultado da implementação deIterator para Counter poderia ser assim, com adefiniçãodeItemsendodotipou32:

///exemploretiradodohttps://doc.rust-lang.org/book/second-editionimplIteratorforCounter{typeItem=u32;

fnnext(&mutself)->Option<Self::Item>{

Além disso, é possível usar trait para fazer sobrecarga deoperadores(operatoroverload).Rustnãopermiteacriaçãode operadores particulares ou todos os que podem serencontrados em outras linguagens, mas permiteimplementaçõesdelespormeiodabibliotecastd::ops.Isso se dá pela implementação das traits presentes emstd::ops.Oexemploaseguirfoiretiradodorepositóriohttps://github.com/GodiStudios/mathf, e remete asobrecargadosoperadoresADD eMul para vetores 2D(Vector2):

#[derive(Clone,PartialEq,Debug)]pubstructVector2{x:f32,y:f32,}

//...

54 6.3TÓPICOSESPECIAISEMTRAITS

Page 68: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

implops::AddforVector2{typeOutput=Vector2;

///Implementaatrait'+'paraastrcutVector2fnadd(self,new_vec:Vector2)->Vector2{Vector2{x:self.x+new_vec.x,y:self.y+new_vec.y}}}

implops::Mul<f32>forVector2{typeOutput=Vector2;

///ImplementaamultiplicaçãodeumVector2porumescalar.fnmul(self,value:f32)->Vector2{Vector2{x:self.x*value,y:self.y*value}}}

implops::Mul<Vector2>forVector2{typeOutput=f32;

///ImplementaoprodutoescalardedoisVector2como'*'.fnmul(self,new_vec:Vector2)->f32{self.x*new_vec.x+self.y*new_vec.y}}

#[cfg(test)]modtests{usesuper::*;

//...

#[test]fnadds_right_and_left_vectors(){letactual=Vector2::RIGHT()+Vector2::LEFT();assert_eq!(actual.x,0f32);}

#[test]fnadds_right_and_up(){letactual=Vector2::RIGHT()+Vector2::UP();assert_eq!(actual.x,1f32);assert_eq!(actual.y,1f32);}

6.3TÓPICOSESPECIAISEMTRAITS 55

Page 69: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

#[test]fnmult_one_by_3(){letactual=Vector2::ONE()*3f32;assert_eq!(actual.x,3f32);assert_eq!(actual.y,3f32);}

#[test]fndot_product(){letvec1=Vector2{x:2f32,y:1f32};letvec2=Vector2{x:1f32,y:2f32};letactual=vec1*vec2;letexpected=4f32;assert_eq!(actual,expected);}

//...}

Podemosverqueatraitops::Add(+)paraasomadedoisvetores é constituída de um tipo de saída Output Vector2 .Portanto, quando fizermos Vector2 + Vector2, teremos umnovoVector2.

A segunda implementação remete a multiplicação de umVector2 por um tipo f32 , que retorna outro f32 . Éinteressante notar que, neste caso, temos a declaração daimplementação impl ops::Mul<f32> for Vector2 , queimplementa o operador Mul para Vector2 quando émultiplicadoporumtipof32.

Jánaúltimaimplementação,temosumoutrocasointeressante.Oprodutoescalardedoisvetores,noqualVector2MulVector2= f32 , foi definido o tipo de retorno como um f32 , e adeclaração da implementação de Mul ficou impl

ops::Mul<Vector2> for Vector2 , que significa a

56 6.3TÓPICOSESPECIAISEMTRAITS

Page 70: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

implementaçãodooperadorMulparadoisVector2.Nofimdalistagem, podemos observar alguns testes que foramusados paradesenvolverassobrecargas.

Agora podemos avançar para os próximos passos daprogramação funcional. Para isso, gostaria de mostrar doisshortcutsdalinguagemqueacreditoseremmuitoúteis.

Informações sobre tipos – https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html

Parasabermais

6.3TÓPICOSESPECIAISEMTRAITS 57

Page 71: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO7

AprimeiravezquevioifleteowhileletfoilendoolivroThe JoyofClojure e,naépoca,acheiumasoluçãobrilhante.Porém,descobriqueoSwifttambémpossuiumafeatureparecida.AinclusãodestetópicocomoprogramaçãofuncionalsedeupeloquepercebidesuasaplicaçõesemClojure.

Partindoparaumpoucodedetalhes,if-letewhile-letsão macros em Clojure, e são muito úteis para caso você sintanecessidadedeatribuirovalordeumaexpressãoaumavariável.Isto evita a necessidade deif/while encadeados comlets,comomostraoexemploaseguir:

EmRust,oifleteowhileletescritosseparados.MasemClojure, por se tratar de umamacro, utilizamos o hífen(if-let).

(if:true(let[resultado:true](printlnresultado)));;=>:true

Comamacroif-let,teríamos:

(if-let[resultado:true](printlnresultado))

IFLETEWHILELET

58 7IFLETEWHILELET

Page 72: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

;;=>:true

Em Rust, existem soluções assim também: o if let e owhilelet.

Asintaxedoifletpermitecombinaroifcomoletdeumaformamenosverbosa,especialmenteseoobjetivoéfazerumpatternmatching(correspondênciadepadrões).Porexemplo,vejaalistagemaseguir:

letvalor=Some(0u8);matchvalor{Some(8)=>println!("imprime8"),_=>(),//nãofaznada.}

O objetivo dessa listagem é imprimir 8 se valor ==

Some(8);casocontrário,nãofazernada.Portanto,precisamosdo_=>()paraquetodososcasosdomatchsejamsatisfeitos,oque adiciona um boilerplate desnecessário. Assim, poderíamosresolveroseguintecódigodeformamaissimplescomumsimplesiflet:

ifletSome(8)=valor{println!("imprime8");}

Podemos ver que oiflet torna o patternmatching bemmenosverboso,poisrecebeumpadrãoeumaexpressão,separadosporum=,e realizaacondiçãocasosejaverdadeira.Algunsdosbenefícios são menos digitação, menos indentações, maisconsistência e menos boilerplate – um syntax sugar do matchparacasosespecíficos.

7.1IFLET

7.1IFLET 59

Page 73: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Poroutrolado,perdemosaverificaçãoexaustivaqueomatchrealiza.Portanto, épreciso ter cuidadonahorade escolher entreifletoumatch, tendoconsciênciadequeexistemganhoseperdas.

Patternmatching(oucorrespondênciadepadrões)éoatodeverificarseumasequênciadevalorespossuiumdeterminadoconjuntodepadrões. Já syntax sugar (ou açúcar sintático) éumpadrãodedesigndecódigoquetemcomoobjetivotorná-lomaissimplesefácildeler.

Felizmente, existem algumas soluções para validar casos nãotestados pelo if, o if let else. Imagine que temos umcontadorequequeremosqueelereajadeformadiferenteemcasosespecíficos. Por exemplo, queremos verificar se o valor é do tipoSome(x)parachamarfoo(x);senão,chamamosbar().

ifletSome(x)=contador{foo(x);}else{bar();}

Assim,entende-sequeoiflet desestruturaocontadoremSome(x): se forverdadeiro,avaliaobloco{foo(x)}; casocontrário, retorna o bloco {bar()} . Além disso, é possíveladicionarumelseifnomeiodocaminhocomoumaopçãodeverificação.

7.2IFLETELSE

60 7.2IFLETELSE

Page 74: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Deformasimilar,owhileletpodeserusadoparafazerumloop condicional enquanto um padrão seja satisfeito. Assim,teremos:

letmutv_primos=vec![2,3,5,7,11];loop{matchv_primos.pop(){Some(x)=>println!("Estenúmeroéprimo={}",x),None=>break,}}

Issosetransformariaemalgocomo:

letmutv_primos=vec![2,3,5,7,11];whileletSome(x)=v_primos.pop(){println!("Estenúmeroéprimo={}",x);}

Agoraqueentendemosumpoucomaissobreassimplificaçõespermitidas no desenvolvimento de patterns em Rust, podemosterminar o tópico de programação funcional com iterators,adapterseconsumers.

7.3WHILELET

7.3WHILELET 61

Page 75: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO8

Programação funcional, como já foi "não definida"anteriormente, possui uma série de características. Tendo comoreferência o Clojure, uma linguagem de programação funcionalimpura–naqualtodasassequênciassãoavaliadaseosdadossãoimutáveisporpadrão–,háfacilidadedesetrabalharcomlaziness,de implementar funções puras e de trabalhar com funções deordemsuperior.

As linguagens de programação que dizem aplicar conceitosfuncionais trazem,namaiorparte,simplificações terríveisdoqueseriaoparadigmafuncional.Programaçãofuncionalnãosãomaps,filtersereduces;nãoétipagemneméapresençadelambdas,massãoconceitospresentesemmuitasdaslinguagensfuncionais.

Nessesentido,parecequeRusttomaumcaminhosemelhanteao do Clojure, já que, ao usarmos esses conceitos, conseguimosagregarmais valor a suas funções. A seguir, veremos os tópicosimportantesaosediscutirsobreprogramaçãofuncionalemRust,especialmente por afetar os conceitos de iterators, adapters econsumers.

ITERATORS,ADAPTERSECONSUMERS

62 8ITERATORS,ADAPTERSECONSUMERS

Page 76: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Imutabilidade: como mencionado no capítulo O que éprogramação funcional?, se nada é imutável, não temoscomogarantirquenãoocorramefeitoscolaterais.Assim,sevalores não são puros, nada poderá ser. Além disso, alinguagem também não permite criar variáveis mutáveisacidentalmente e, para consumi-las, é preciso explicitar.Sendo assim, Rust toma uma abordagem semelhante emrelaçãoaosefeitoscolaterais.

Selfemtraits:ousodeself,implícitoouexplícito,éumgrande efeito colateral, e Rust aplica esse conceito emdiversos lugares, especialmente nas implementações dastraits.Portanto,emumasituaçãoruim,omelhoréquesejaexplícito.

Mocking: Rust não aplica conceitos de mocking. Aoobservamososmockssobosolhosdeefeitoscolaterais,elessão uma grande bandeira vermelha de código impuro,portanto, aos olhos da programação funcional, são umsinaldequealgoestáerrado.

Uma vez um colega de trabalho javeiro me perguntoucomo se fazmocks emClojure,mas a resposta équenãoaplicamosmocksemClojure,poisanecessidadedeleséumsinaldequeprecisamosrefatorarocódigo–eessamesmapercepçãoexistenoRust.

Portanto, neste capitulo, vamos partir do pressuposto de queprecisamos explorar em programação funcional a facilidade comquepodemosoperarcálculossobrecoleções,independentedeseutipo. Uma lista de variáveis é conhecida como vector em Rust,abreviada para vec . E ao aplicar técnicas de programação

8ITERATORS,ADAPTERSECONSUMERS 63

Page 77: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

funcional, é possívelmanipular vetores (ououtras coleções) compouquíssimo código, evitando os tradicionais loops (for). Issopodeserresolvidocomaaplicaçãodemaps,filters,zipsetc.

EmRust,otipoIteratoréresponsávelpelamaiorpartedo"trabalho pesado" funcional. Aplicando as funções iter() einto_iter(), qualquer vetor pode ser transformado em umIterator.Agrandediferençaentreasduasécomoelaspassamosvaloresparaele:

iter() – Passa os valores por referência, evitando acriação,muitasvezesdesnecessária,decópia.into_iter() – Passa os valores por cópia, copiandoelementoaelemento.

É importante lembrar de que isso é feito de forma lazy,portanto,osdadossomentesãoconsumidoscasosejanecessário.

8.1ITERATORS

64 8.1ITERATORS

Page 78: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

COMOFUNCIONAUMITERATOR

Uma boa aproximação é pensar nas funções usadas pelosIterators como "loops (for) especializados" que agemdeforma recursiva no vetor ao aplicar uma série de funções next() com closures como argumento. Assim, cadapassagempelasfunçõesretornaoutroIteratorcomtodosos resultados. Independente da quantidade de funções queforemchamadas,elecontinuarásendoumIteratoratéqueafunçãocollect()sejachamada.

Como um Rust utiliza o modelo lazy de programaçãofuncional, somente computará o que for preciso, igual aoHaskell.Ouseja,somentefaráomínimodecálculospossíveisparaatingiroresultadoesperado.

Amaiorpartedosexemplosquepossopensarutilizaiter().Por exemplo, quando chamamositer() em vetores ou slices,isso cria um tipo Iter<'a, T> , que implementa a traitIteratorreponsávelporpermitirqueapliquemosfunçõescomomap().

Vale lembrar de que Iter<'a, T> tem somente umareferênciaaotipoT,quesomentevaipermitiriterarsobreotipoT no qual foi aplicado o Iter. Um exemplo seria saber aquantidadedeletrasquemeunometem:

fnmain(){

Quandoaplicariter()?

8.1ITERATORS 65

Page 79: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

letmeu_nome=vec!["Julia","Naomi","Boeira"];

letnum_letras=meu_nome.iter().map(|nome:&&str|nome.len()).fold(0,|soma,len|soma+len);

assert_eq!(num_letras,16);}

Nesteexemplo,acombinaçãodemapcomfoldnospermitecontaraquantidadedebytes(stringsRustsãoUTF-8)detodasasstring no vetor. Sabemos que a função len pode utilizar umareferênciaimutável,porissoécorretousaroiter() emvezdeiter_mutouinto_iter.

Além disso, isso nos permitemover o vetor meu_nome emoutro momento, caso seja necessário. A closure aplicada no map() não exige que tipemos, mas, por motivos didáticos,optamosportiparparafacilitaracompreensãodoqueestásendopassado.

Algocuriosonesteaspectoéqueotipodenomeé&&str, enão&str.Issoocorreporqueotipodemeu_nomeé&str,mas,apósaplicarmositer(), recebemos a referêncianome&&strdoIterator demeu_nome&str. É interessante pensar emtiparclosurequandoqueremosaplicarumadesestruturação.Casosejanecessáriomutardadosduranteummap,épossívelutilizaroiter_mut().

O into_iter() deve ser usado quando queremos mover(move) o valor em vez de pegá-lo emprestado.Ointo_iter()cria o tipo IntoIter<T> , que possui ownership dos valores

Quandoaplicarinto_iter()?

66 8.1ITERATORS

Page 80: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

originais.

Como anteriormente, é o IntoIter<T> que implementa otrait Iterator . A palavra into geralmente representa que avariável T está sendo movida. A aplicação mais clara de into_iter() é quando existe uma função transformandovalores.

fnnome(v:Vec<(String,usize)>)->Vec<String>{v.into_iter().map(|(name,_score)|name).collect()}

fnmain(){letvec=vec!(("Julia".to_string(),10));letnome=nome(vec);

assert_eq!(nome,["Julia"]);}

A ideia de usar into_iter() aqui é o fato de que a tupla(String,usize)étransformadaemstring.

O tipo Iterator contém algumas funções centrais, comomap(),filter(),collect(),fold() emuitas outras quefalaremosadiante.Creioqueestas4sejamasmaisrepresentativaseumaótimaintrodução:

Map aplica uma closure a cada elemento de um vetor, eretornaesteresultadoparaapróximafunçãoaseraplicada.Filtersfuncionamdeformaparecida,porém,elesretornamsomente os elementos que satisfazem algum critérioespecífico.

8.2MAPS,FILTERS,FOLDSETUDOMAIS

8.2MAPS,FILTERS,FOLDSETUDOMAIS 67

Page 81: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Fold aplica uma closure cujo propósito é acumular, dealgumamaneira,todososelementosemumaúnicalinha.Ao final de todas as chamadas de funções, a funçãocollect() é utilizada para retornar um novo vetor devalores.

Agorapodemosdefinirumvetor,como:letvector=vec!(1,3,4,5,3); . Queremos mapear todos estes números paraserempares,utilizandomap:

letvetor_pares=vector.iter().map(|&x|x*2).collect::<Vec<i32>>();

Ou talvez queremos filtrar todos aqueles que nãocorrespondemàcondiçãodevalorespares,usandofilter:

letvalores_pares=vector.iter().filter(|&x|x%2==0).collect::<Vec<&i32>>();

Ou simplesmente queremos contar quantos pares temos novetor?Logo,usaríamoscount.

letcontagem=vector.into_iter().filter(|x|x%2==0).count();

Épossíveltambémencontrarasomadetodosositensdovetor,comfold:

letsoma=vector.iter().fold(0,(|acc,valor|acc+valor));

Podemosaplicarumzip:

letindex_vec=0..contagem;letindexed_vector=vector.iter().zip(index_vec).collect::<Vec<(&i32,usize)>>();

Oudescobrirovalormáximodeumvetorcommax():

letmax=vector.iter().max().unwrap();

68 8.2MAPS,FILTERS,FOLDSETUDOMAIS

Page 82: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Um bom primeiro exemplo seria criar um vetor com os 5primeirosnúmerosímparesaoquadrado,apartirdorangede1aoinfinito.

fnmain(){letvector=(1..).filter(|x|x%2!=0).take(5).map(|x|x*x).collect::<Vec<usize>>();assert!(vec![1,9,25,49,81]==vector);}

Éinteressantepercebemosque,apósasequência(1..), nãotemos um iter ou into_iter. Isso se deve ao fato de querangesjásãooperadasdeformalazy,porpadrão.Nossofilterprocuraporelementosnãopares,eotakeretiraos5primeiros.Nelesafunçãomap retornaoquadradodecadaum,ea funçãocollectnosretornaumvector.

Agora imagine que queremos manipular meu nome, paraencontrarminhaletrapreferida,oo:

fnmain(){letnome="JuliaNaomiBoeira";letnomes:Vec<&str>=nome.split_whitespace().collect();letnomes_com_o:Vec<&str>=nomes.into_iter().filter(|nome|nome.contains("o")).collect();assert!(vec!["Naomi","Boeira"],nomes_com_o);}

Exemplosmaiselaborados

and_then

8.2MAPS,FILTERS,FOLDSETUDOMAIS 69

Page 83: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Para o tipo Result , Rust possui um adapter muitointeressante,oand_then().Trata-sedeumafunçãochamadase,e somente se,oResult fordo tipoOK().Agora imaginequequeremosescreverumafunçãoqueeleveumnúmeroaoquadrado:

letresultado:Result<usize,&'staticstr>=Ok(2);letvalor=resultado.and_then(|n:usize|Ok(n*n));assert_eq!(Ok(4),valor);

Caso a primeira linha recebesse um Err , a closure |n:usize|Ok(n* n) não seria chamada, como demonstramos aseguir:

letresultado:Result<usize,&'staticstr>=Err("Erro");letvalor=resultado.and_then(|n:usize|Ok(n*n));assert_eq!(Err("Erro"),valor);

Agora pensei que queremos aninhar and_thens. Podemosverumbomexemplodissoaotentarmosrealizarumainversãodeumnúmeron,demodoqueele fique1/n.Temosde garantirqueovalornnãoézero,comooexemploaseguirnosmostra:

fnmain(){letres:Result<usize,&'staticstr>=Ok(0);

letvalor=res.and_then(|n:usize|{ifn==0{Err("Numeronaopodeserzero")}else{Ok(n)}}).and_then(|n:usize|Ok(1f32/nasf32));//<-closurenãoéchamado

assert_eq!(Err("Numeronaopodeserzero"),valor);}

Antes de partir para o último tópico, vale conhecermos o

70 8.2MAPS,FILTERS,FOLDSETUDOMAIS

Page 84: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

adapteror_else, chamado somentequandooErr() ocorre.Eleémenosfrequente,masmuitobomdeseteremmente,poiséusadodamesmaformaqueoand_then.

No código a seguir, definimos duas funções que vão nosretornar dois valores Option , e faremos 3 testes comassert_eq! . No primeiro, nosso Option possui um tipodefinido Some(Barbaros) e temos como retornoSome(Barbaros).Agora, senossovaloréumNone, podemosretornarumtipoSome(romanos)ououtroNone.

fnnao_identificado()->Option<&'staticstr>{None}fnromanos()->Option<&'staticstr>{Some("romanos")}

assert_eq!(Some("Barbaros").or_else(romanos),Some("Barbaros"));assert_eq!(None.or_else(romanos),Some("romanos"));assert_eq!(None.or_else(nao_identificado),None);

Decertaforma,Consumersjáforamexplicadosnosexemplosanterioresquandousamosafunçãocollecteafunçãofold.Sua principal função é retornar uma sequência ou um tipo, deacordo com a iteração lazy gerada pelos iterators e adapters.Portanto,paraumiteratorretornarumvalor,énecessárioutilizarfunçõescomoessascitadas.

Umbomexemploemrelaçãoaofoldpode ser feito comademonstração a seguir, que nos apresenta uma comparação dasomadentrodeumfor,pelosoma_for, e a somadofold,comosoma_fold.Nossofoldrecebedoisparâmetros:ovalorinicial,quevamosacumular(nonossocaso,zero),eumaclosure,quetemcomoprimeiroparâmetroovaloracumulado.

8.3CONSUMER

8.3CONSUMER 71

Page 85: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

letnumeros=[1,2,3,4,5];

letmutsoma_for=0;

foriin&numbers{soma_for+=i;}

letsoma_fold=numero.iter().fold(0,|acumulador,&x|acumulador+x);

assert_eq!(soma_for,soma_fold);

Ocollect transforma o iterator e seus adapters em umacoleção que é retornada. Temos uma lista de caracteresaparentementedesconexasobreaqualvamositerar.

O primeiro map transforma todos os nossos caracteres emu8. Já o segundo adiciona 1 a esses valores e devolve-os aoformatochar.Para finalizar, aplicamosumcollect, emquenão precisamos explicitar um tipo, pois já foi explicitado emString,queporumacasoéumacoleção.

letchars=['g','d','k','k','n'];

lethello:String=chars.iter().map(|&x|xasu8).map(|x|(x+1)aschar).collect();

assert_eq!("hello",hello);

Agora conseguimos entender como utilizar funções emdiversos aspectos, aplicá-las a tipos diferentes e especialmenteiterar sobre sequências de coleções com iterators, adapters econsumers. Assim, conseguimos muitas formas de simplificar aforma como iteramos sobre estruturas de dados, o que facilitarámuitoa formacomo lidamoscomrequests ereponses nas

72 8.3CONSUMER

Page 86: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

diversaslibsdisponíveisnoRust.

Especificações da documentação do Iterator –https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html

Parasabermais

8.3CONSUMER 73

Page 87: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Oqueéprogramaçãoconcorrente?Threads—AbasedaprogramaçãoconcorrenteEstadoscompartilhadosComunicaçãoentrethreads

Programaçãoconcorrente

Page 88: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO9

Aseguir,seráfeitoumapproachbaseadoemClojure.

MAISDOISDISCLAIMERS

O slogan do Rust é Fearless Concurrency, ou seja,concorrênciasemmedo.Por causa deste slogan, optei por comparar com alinguagemqueconheço,queresolvedamelhorformapossíveisproblemasdeconcorrência,oClojure.Claroque Go, C#, Python etc. têm suas soluções, masnenhumadelas resolveda formacomoeucreio seramelhor.

Existem 3 termos que muitas vezes são associados aosdomínios da programação concorrente, mas que representamcoisas diferentes: concorrência, paralelismo e programaçãodistribuída.Aconcorrênciabaseia-seemrealizardiferentestarefas

OQUEÉPROGRAMAÇÃOCONCORRENTE?

9.1DEFINIÇÃODECONCORRÊNCIA

9OQUEÉPROGRAMAÇÃOCONCORRENTE? 75

Page 89: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

em threads diferentes. O paralelismo baseia-se em quebrar umatarefa em diferentes threads. Já a programação distribuídaconsidera o domínio de redes de computadores atuando comoumaúnicamáquina.

O foco neste momento é a programação concorrente, querepresenta um sistema desenhado para realizar processos lógicosde forma independente. A ideia por trás é que a concorrênciadispara tarefas aomesmo tempo,dividindo recursos emcomum,masnãonecessariamenterealizandotarefasrelacionadas.

Como ponto central do Clojure está o gerenciamento deestados,queporsuavez levaaumaexcelente formade trabalharcom concorrência. Considerando que todos os valores sãoimutáveis,háumagrandefacilidadedeprontamentecompartilharesses valores entre asmais diferentes threads. Além disso, comoveremosmais adiante,Clojure possui tipos específicos para cadasituação, considerando a necessidade de sincronicidade,assincronicidade,independênciaecoordenação.

Emoutros casos, como o Java, que opera em ummodelo deconcorrência de estados compartilhados, é necessário um grandemalabarismo para gerenciar os locks que protegem os dadoscompartilhados.No Java, pormais que você consiga se localizarcom todos os locks, ele raramente escala a qualquer pontosaudável.

JáaSTM(SoftwareTransactionalMemory)doClojurepermite

9.2 POR QUE CLOJURE É UMA BOAREFERÊNCIADECOMPARAÇÃO?

76 9.2PORQUECLOJUREÉUMABOAREFERÊNCIADECOMPARAÇÃO?

Page 90: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

uma forma não bloqueante de coordenar updates em valores decélulasmutáveis, jáquemutaçõessãopermitidassomentedentrodeumamesmatransação.

Atualmente, há quatro tipos de referência para auxiliar aprogramação concorrente: refs, agents, atoms e vars. A tabela aseguirlistasuasprincipaispropriedades,masessencialmentetodas— exceto vars — são referências compartilhadas e permitem avisualizaçãopelathread.

Vejaatabeladetiposdereferênciaporpropriedades:

Ref Agent Atom Var

Coordenada x

Assíncrona x

Repetível x x

Thread-Local x

UmdospontosinteressantesdeseobservaraoutilizaraSTMéafaltadelocks(outravas),jáquenãoháchancesdedeadlocks.Omais interessante é que, quando uma identidade é requisitada, atransaçãorecebeuma"foto",umacópiadoatualestadodoinstantedas propriedades. Porém, não é necessariamente a foto maisrecente, mas sim a foto coerente com o estado da thread. Elapermite que não existam conflitos entre transações, o que evitadeadlocks.

ComooClojurefaz?

9.3EORUST?COMOFICA?

9.3EORUST?COMOFICA? 77

Page 91: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

AsegurançadememóriadoRusttambémseaplicaàsquestõesdeconcorrência,jáqueatémesmoprogramasconcorrentesdevemser seguros, impedindo a existência de data races. O sistema detipos do Rust consegue garantir muita coisa no momento decompilar.

Alémdisso,Rustpermitequevocê implemente suasprópriassoluções de concorrência. Um desafio interessante seriaimplementaralgumaqueapliqueSTMcomoemClojure(oportdeClojure para CLR tem tido dificuldades para manter a mesmaconcepçãodaSTMparaaJVM).

ConcorrênciasegurasemprefoiumaprioridadeparaaequipedoRust,mas omais incrível é que essa linguagem permite umagrandevariedadedeabstraçõesdeparalelismo.Essavariedadetemcomo objetivo garantir a ausência de data races, sem empregarcoletores de lixo, algo que nenhuma outra linguagem conseguiufazer.

78 9.3EORUST?COMOFICA?

Page 92: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

THREADS

Aunidade de computação emdiferentes núcleos emRust échamadadethread,assimcomoemmuitasoutraslinguagens,queéumtipodefinidopelabibliotecapadrãostd::thread.Cadathreadpossuisuaprópriastackeumestadolocal.

ORustpermiteacriaçãodemuitasthreads,ecadaumapodesermãedemuitasoutrasthreadsfilhas.Vejaoquepodeserfeitocomdadosnasthreads:

Dados podem ser compartilhados por diferentesthreads, quando queremos um estado compartilhadomutável.Dadospodemserenviadospormeiodethreads,com"comunicação".

Como jámencionado anteriormente, oRust foi desenvolvidoparasolucionardoisproblemasemespecial:

Comoprogramarsistemasdeformasegura?Comofazeraconcorrêncianãovirarumadordecabeça?

Inicialmente, esses problemas foram tidos como ortogonais,mas a equipe de desenvolvimento percebeu que a solução era amesma, e que o problema era que: bugs de memória e bugs deconcorrência geralmente são causados por acessar informaçõesquandonãodeviam.Assim,asoluçãofoiosistemadeownership,

9.4RUST:CONCORRÊNCIASEMMEDO

9.4RUST:CONCORRÊNCIASEMMEDO 79

Page 93: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

algo que o compilador realiza, e não algo que os programadoresdevemteremmente.

Paraasegurançadememória,issosignificavaqueoscoletoresde lixoeramdesnecessários equeoprogramanãoacessariaumamemória não permitida (segfaults). Para a concorrência, issosignificavaquenãoexistiramasarmadilhastriviais, independentedo paradigma que fosse aplicado — como mensagens, estadoscompartilhados,semlocks,funcionaletc.

Algumasdassoluçõesqueapareceramforam:

Channels (canais) transferem ownership por meio demensagensenviadas,sendopossívelenviarumponteirodeuma thread a outra sem medo de dar a face quando oponteiro for acessado. Os canais procuram reforçar osconceitosdeisolamentodethreads.

Um lock sabe qual dado tem de proteger, e Rust garanteque um dado somente será acessado quando o lock formantido. Assim, estados nunca são compartilhadosacidentalmente. A filosofia por trás das locks é: segure(lock)dadosenãocódigo.

Cada tipo de dado sabe se pode ser enviado ou acessadoentre múltiplas threads. Não há data races, mesmo paraestruturas de dados que não necessitam locks. Segurançaemthreadsélei,enãoapenasdocumentação.

Vocêpodeatécompartilharstackframesentrethreads,queo Rust assegurará estaticamente que os framespermaneçamativosenquantooutrasthreadsestãousando-os. As formas de compartilhamento mais ousadas são

80 9.4RUST:CONCORRÊNCIASEMMEDO

Page 94: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

garantidasemRust.

A partir de agora, veremos de forma mais profunda aconcorrência em Rust, iniciando por threads, depoiscompartilhamento de estados em threads e, por fim, channels.Porém, é interessante trazer alguns pontos que tornam aconcorrênciaatrativa,comoquandoumjogoatualizaoestadodecentenas de unidades ao mesmo tempo, enquanto toca umamúsica de fundo. Esse exemplo poderia muito bem quebrar osváriosestadosemdiferentesthreads.

Ouentão, imagineumprogramade inteligência artificialquedivide seus cálculos entre os váriosnúcleospara ter umamelhorperformance; ou um servidor web que lida com mais de umrequestaomesmotempocomoobjetivodemaximizarsuassaídas,assim como um servidor que precisa paralelizar funçõesindependentes.Ou, por fim, imagine um caso clássico em que ainterface do usuário continua funcionando perfeitamenteenquantooprogramafazdiversosrequestsaomesmotempo.

9.5QUANDOUTILIZARCONCORRÊNCIA?

9.5QUANDOUTILIZARCONCORRÊNCIA? 81

Page 95: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO10

Jámencionei o básico sobre o que é uma thread no capítuloanterior, então agora podemos partir direto para o que importa:criaruma thread. Isso é feito pela funçãothread::spawn, quepermite criar uma thread filha capaz de vivermais que a threadmãe.

Aprincipal threadmãeéamain, que foi criadadeclarandofnmain(){...},geralmentecomaopção--binnoCargo.Oexemploaseguirmostracomoissofunciona:

usestd::thread;

fnmain(){thread::spawn(move||{println!("Olavindodathreadfilha");});thread::sleep_ms(100);println!("Olavindodathreadmãe");}

Podemosverquea funçãospawn recebeuma closure comoargumento,edeveexecutarathreadfilhadeformaindependente

THREADS—ABASEDAPROGRAMAÇÃOCONCORRENTE

82 10THREADS—ABASEDAPROGRAMAÇÃOCONCORRENTE

Page 96: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

dathreadmãe(nestecaso,main()).Éimportantesalientarqueaclosure possui omove, pois a thread necessita ter propriedadesobreseuselementos.

Notequeexisteumaoutrafunçãonoexemplo,asleep_ms.Ela está presente, uma vez que a thread main é a pior mãepossível:quandoelaencerra,suasfilhastambémsãoencerradas.

Parasolucionaresteproblema,adicionamososleep_ms,quegarante tempo para a thread filha encerrar. Como já mencioneianteriormente, thread filhaspodem, emgeral, viver alémde seuspais,tornandodesnecessáriaafunçãosleep_ms,porémamainfogeaessaregra.

FUNÇÃOSPAWN

fnspawn<F,T>(f:F)->JoinHandler<T>whereF:FnOnce()->TF:Send+'static,T:Send+'static

Send—Éummarkertrait,oquesignificaquenãoimplementanenhummétodo,somentesinalizaqueovalorésegurodeserpassadoporthreads.Alémdisso,éimplementadoautomaticamenteparaestruturasqueo compilador percebe não ter problemas para enviarentrethreads.JoinHandler—Pode serusadopara sincronizar aexecuçãodediferentesthreads.AJoin() exigequeasthreadsterminem.

Utilizar o sleep_ms resolveu nosso problema, mas tornou

10THREADS—ABASEDAPROGRAMAÇÃOCONCORRENTE 83

Page 97: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

nosso código grosseiro e feio.Para isso, existeuma soluçãomaiselegante, que consiste em usar o Join Handle, que é a variávelgeradapelospawn.

Portanto, chamar o método join() na variável handlegerada pela thread é uma forma de garantir sua execução, poisbloqueiaathreadmãeatéqueafilhaseencerre.OvalorderetornoseráumResult,enestecasooOKserádotipo()ouvoid,jáquenossathreadnãoretornaumvalor.Vale lembrartambémdequeResultéumenumcomasopçõesOKeErr.

usestd::thread;

fnmain(){lethandle=thread::spawn(move||{println!("Olavindodathreadfilha");});

println!("Olavindodathreadmãe");letoutput=handle.join().unwrap();println!("Athreadfilharetornou{:?}",output);}

Outramaneirade resolver isso seriautilizar ojoin() logodepoisde lançara thread.Entretanto, issotornariaaexecuçãodenosso código síncrona, o que só teria sentido se não fôssemoslançarnenhumaoutrathread.Ficariaassim:

thread::spawn(move||{println!("Olavindodathreadfilha");}).join();

Como já mencionei anteriormente, cada thread tem seuprópriostackeestadolocale,porpadrão,nenhumainformaçãoé

10.1LANÇANDOMUITASTHREADS

84 10.1LANÇANDOMUITASTHREADS

Page 98: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

compartilhada entre elas se não forem imutáveis. O processo deiniciar threads é bastante simples. O exemplo a seguir cria10_000threads:

usestd::thread;staticDEZ_MIL:i32=10_000;

fnmain(){foriin0..DEZ_MIL{let_=thread::spawn(move||{println!("Chamandoathreaddenúmero{:?}",i);});}}

A execução dessas threads ocorre de forma independente,portanto,osresultadospodemocorrerdeformadesordenada:

Chamandoathreaddenúmero2Chamandoathreaddenúmero1Chamandoathreaddenúmero4Chamandoathreaddenúmero6Chamandoathreaddenúmero3Chamandoathreaddenúmero5...

10.1LANÇANDOMUITASTHREADS 85

Page 99: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

QUANTASTHREADSDEVEMOSLANÇAR?

ExisteumaregraqueonúmerodethreadsdeveseromesmonúmerodenúcleosdaCPU,oupelomenosmuitopróximodisso.ORustpossuiumacratequesolucionaesseproblema,anum_cpus, quepermitedeterminarosnúmerosdenúcleosdaCPU.Para utilizá-la, adicione as seguintes linhas em seuCargo.toml:

[dependencies]num_cpus="*"

Assim,podemosreescreverocódigoanterior,considerandoonúmerodenúcleos:

externcratenum_cpus;usestd::thread;

fnmain(){letncpus=num_cpus::get();foriin0..ncpus{let_=thread::spawn(move||{println!("Chamandoathreaddenúmero{:?}",i);});}println!("Totaldenúcleos{:?}",ncpus);}

Outradependênciainteressantedeseutilizaréathreadpool.Ésó adicionar threadpool = "*" às dependências doCargo.toml.Suafunçãoéexecutarumnúmerodetarefassobreumconjuntopré-definidodethreadsparalelas.

Além disso, a threadpool é capaz de reabastecer a pool com

86 10.1LANÇANDOMUITASTHREADS

Page 100: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

novas threads, caso ocorra algum panic. Então, teríamos algoassim:

externcratenum_cpus;externcratethreadpool;

usestd::thread;usethreadpool::ThreadPool;

fnmain(){letncpus=num_cpus::get();letpool=ThreadPool::new(ncpus);

foriin0..ncpus{pool.execute(move||{println!("Chamandoathreaddenúmero{:?}",i);})}thread::sleep_ms(100);}

O que acontece quando uma thread lança um panic ?Essencialmente, não acontece nada, já que threads são isoladasumas das outras. Somente a thread que lançou esse panic vaicolapsarapósliberarseusrecursos,nãoafetandoathreadmãe.

Entretanto, ela pode testar o valor de retorno com a funçãois_err(),comomostraoexemploaseguir:

usestd::thread;

fnmain(){letresultado=thread::spawn(move||{panic!("Oh!Nao!Estouempanico");}).join();

ifresultado.is_err(){println!("Seufilhoentrouempanico!");

10.2PANIC!ATTHETHREAD

10.2PANIC!ATTHETHREAD 87

Page 101: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

}}

Oresultadoserá:

thread'<unnamed>'panickedat'Oh!Nao!Estouempanico',src/main.rs:5note:Runwith`RUST_BACKTRACE=1`forabacktrace.Seufilhoentrouempanico!

Programaçãomultithreadsétradicionalmentemuitocomplexase você permitir que as diferentes threads compartilhem asmesmasvariáveismutáveis, istoé,aditamemóriacompartilhada.Quando duas oumais threadsmodificam simultaneamente umavariável, ocorre a formaçãodedados corrompidos, asdata races.Issoacontecedevidoàfaltadeprevisibilidadedocomportamentodediferentesthreads.

Threadssãotidascomosegurasquandodiferentesthreadsnãocausam corrompimento dos dados que acessam. E é exatamenteissooqueocompiladordoRust impedequeaconteça,aplicandoosprincípiosdeownership.

Para exemplificar, imagine o caso em que Goku está sendoatacadopordiferentesCellsJr.Gokupossui1000pontosdevida,ecada Cell Jr. pode lhe causar entre 50 a 200 pontos de dano. Alistagem a seguir mostra o que esperaríamos de umaimplementaçãodesseproblema:

usestd::thread;

fnmain(){letmutvida_goku=1000;forcellin1..5{

10.3THREADSSEGURAS

88 10.3THREADSSEGURAS

Page 102: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

thread::spawn(move||{vida_goku-=cell*50;});}thread::sleep_ms(2000);println!("AvidadeGokuapososataque:{}",vida_goku);}

Mas neste caso vemos que a vida de Goku permanece 1000,mesmo após os ataques. Isso ocorre porque as threads agiramsobreascópiasdosdadosdevida_goku (devidoàaplicaçãodemove),enãonavariávelemsi.Istoé,oRustnãopermitequeasthreads operem sobre uma mesma variável para prevenir dadoscorrompidos.

No próximo capítulo, vamos mostrar como o estadocompartilhadomutávelocorre.

10.3THREADSSEGURAS 89

Page 103: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO11

Como podemos fazer o programa do capítulo anterior nosretornar o valor correto? O Rust possui um tipo específico paraessassituações:osatômicos,std::sync::atomic.

Eles permitem trabalhar sobre os dados mutáveis de formasegura. Para modificar os dados, é necessário encapsulá-los emtiposprimitivosdeSync,comoosArc,Mutex,AtomicUSizeetc.

Deformabastantesimples,aplicamososprincípiosdelock(que o Clojure evita), assim, o acesso exclusivo ao recurso éconcedidoàthreadquepossuiolocksobreorecurso.Nocasodalinguagem Rust, chamamos isso de mutex (em inglês, mutuallyexclusive,emutualmenteexclusiva,emportuguês).

Uma lock será concedida somente a uma thread por vez,impedindo que duas modifiquem o mesmo valor ao mesmotempo, o que evita data races. Quando uma thread termina seutrabalho, a lock é concedida a outra thread. Esse sistema éreforçado pelo próprio compilador. A vida encapsulada peloMutex<T>ficariasemelhanteaisso:

ESTADOSCOMPARTILHADOS

90 11ESTADOSCOMPARTILHADOS

Page 104: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

letvida_atual=Mutex::new(vida_goku);

E em vida atual, aplicaríamos uma lock assim que a threadchamarorecurso:

forcellin1..5{thread::spawn(move||{letmutvida_goku=vida_atual.lock().unwrap();//Restodocódigo});}

Dessaforma,achamadadelock()retornaráumareferênciaao valor contido dentro do Mutex, e bloqueará qualquer outrachamada até que a referência saia do escopo. No atual estado,teremosumerro(captureofmovedvalue:'vida_atual'),que significa que uma variável não pode sermovida para outrasthreadsváriasvezes.

Esseproblemaébastantesimples:todasasthreadsprecisamdereferênciasaosmesmosrecursos(nossavariávelvida_goku).Pararesolverisso,podemosutilizaroconceitodeponteiroRc,masdeformamaissegura,oArc<T>,cujosignificadoéumponteiroRcatômico,oucontadordereferênciasatômico.

AfunçãodoArcé serseguropormeiode todasas threads,conformeoexemploaseguir:

usestd::thread;usestd::sync::{Arc,Mutex};

fnmain(){letmutvida_goku=1000;letrecursos=Arc::new(Mutex::new(vida_goku));

forcellin1..5{letmutex=recursos.clone();thread::spawn(move||{

11ESTADOSCOMPARTILHADOS 91

Page 105: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

letmutvida_goku=mutex.lock().unwrap();*vida_goku-=cell*50;}).join().unwrap();}thread::sleep_ms(2000);vida_goku=*recursos.lock().unwrap();println!("AvidadeGokuapososataque:{}",vida_goku);}

Outraopçãoseriausarexpectemvezdeunwrap,poisissopermite entender no console qual foi e onde ocorreu o erro queobtivemos.Vejaoexemploaseguir:

usestd::thread;usestd::sync::{Arc,Mutex};

fnmain(){letmutvida_goku=1000;letrecursos=Arc::new(Mutex::new(vida_goku));

forcellin1..5{letmutex=recursos.clone();thread::spawn(move||{letmutvida_goku=mutex.lock().expect("Gokusedefendeuatravésdolock");*vida_goku-=cell*50;}).join().expect("Uniãodosgolpesfalhou");}thread::sleep_ms(2000);vida_goku=*recursos.lock().unwrap();println!("AvidadeGokuapososataque:{}",vida_goku);}

92 11ESTADOSCOMPARTILHADOS

Page 106: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

RC(REFERENCECOUNTEDSMARTPOINTER)

Acontagemde referência (significadodeRC) acompanhaonúmerodereferênciasaumvalorparasaberseeleaindaestáem escopo. Se não houver referências a um valor, sabemosquepodemoslimpá-losemqueelassejaminválidas.

Namaioriadoscasos,aownershipébastantetrivial:vocêsabeexatamentequal recursopossuideterminadovalor,mas issonãopodeseraplicadosempre.Àsvezes,vocêpoderealmenteprecisar de vários owners (proprietários). Para isso, o RusttemumtipochamadoRc<T>,eseunomeéumaabreviaturaparaacontagemdereferência.

UsamosRc <T> quando queremos alocar dados na heappara que várias partes do programa possam ler, sem queprecisemosdeterminaremtempodecompilaçãoquaispartesdo nosso programa utilizarão por último. Observe que Rc<T>éapenasparaousoemcenárioscomumaúnicathread.

Nocenárioatual,cadathreadatuaemumacópiadoponteiroobtido pelo método clone() , e a instância Arc manterácontrole sobre todas as referências criadas na vida_goku . Achamada à clone vai incrementar a contagem das referênciasexistentesnavida_goku.

Areferênciamutexsaideescopoassimquea threadtermina,decrementando a contagem de referências, e Arc liberará osrecursosassociadosassimqueacontagemforzero.Alémdisso,a

11ESTADOSCOMPARTILHADOS 93

Page 107: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

resposta que a lock nos retorna é do tipoResult<T,E>, poispode acontecer alguma falha, já que utilizamos a funçãounwrap()eassumimosquetudoestariaemordem.

AfunçãounwrapnosretornaovalorTcasoathreadocorranormalmente,eentraráempânico(panic!)casoaconteçaalgumproblema. Reunimos as threads chamando a dereferência derecursos(*recursos)paraconseguiravida_gokuinterna.

EssemecanismoArc(mutex)érecomendávelparaquandoosrecursos ocupam uma quantidade dememória significativa. Issoocorre porque, comArc, os recursos não serãomais copiadosparacadathread,devidoaofatodeeleatuarcomoreferênciaparaos dados compartilhados, então somente essa referência écompartilhadaecopiada.

UmainformaçãoimportanteéqueArc<T>implementaatraitSync , exigida para que um tipo possa ser usado de formaconcorrente.QualquertipoquecontenhatiposqueimplementemSyncéautomaticamenteSync.Exemplosdissoseriaminteiros,floats, enum, structs e tuples - caso contenham tiposSync emsuasdefinições.

Masaindaprecisamospensaremcomonossasthreadspodemsecomunicar,evamosexplorarissonopróximocapítulo.

94 11ESTADOSCOMPARTILHADOS

Page 108: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO12

A comunicação entre threads pode ser realizada via channels(ou canais). Essa forma permite que as threads transmitammensagens entre elas. Esses channels funcionam como dutosunidirecionaisqueconectamduasthreads.

OsdadossãoprocessadosdeformaFIFO(oprimeiroaentraréoprimeiroasair),eosdadosfluementreasduasthreadspartindodo Sender<T> até o Receiver<T>. Nesse mecanismo, umacópia do dado é feita para ser compartilhada com a threadrecebedora,eporissonãoserecomendausardadosextensos.

Além disso, ultimamente se iniciou uma discussão com oobjetivo de abolir os channels, por conta desse motivo recém-apresentado, mas creio que ele seja uma técnica útil paraprogramas pequenos. A imagem a seguir exemplifica o seufuncionamento;nocaso,criamosasthreadsapartirdamain,maselaspoderiamserdesconectadas.

COMUNICAÇÃOENTRETHREADS

12COMUNICAÇÃOENTRETHREADS 95

Page 109: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Figura12.1:Desenhoexemplificandochannels

Vamosaoqueimporta:comousamoschannels?Paracriarumchannel, épreciso importaro submódulompsc (multi-producersingle-consumer communication primitives, ou primitivos decomunicação produtor múltiplo consumidor único) dastd::sync.Comisso,bastachamarométodochannel:

usestd::thread;usestd::sync::mpsc::channel;usestd::sync::mpsc::{Sender,Receiver};

fnmain(){let(tx,rx):(Sender<f32>,Receiver<f32>)=channel();}

12.1CRIANDOCHANNELS

96 12.1CRIANDOCHANNELS

Page 110: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Este código gera uma tupla de terminais tx (t detransmissão),querepresentaoSender(quemenvia),eorx(rde recebedor), que representa o Receiver (quem recebe).Aplicandof32 ao genérico T, determinamos que o channelenviaráinformaçõesdotipof32,porém,asanotaçõesdetiponãosão compulsórias caso o compilador consiga inferir o que estásendopassado.

Para podermos enviar dados por channels, é preciso garantirque o que estamos enviando implemente a traitsend, pois elagarante a transferência de ownership segura entre as threads.Dados que não implementam a trait send não conseguem sertransmitidosviachannels—felizmente,nãoéocasodof32.

No código seguinte, definimos um channel com as variáveistx:Sender<f32>erx:Receiver<f32>.Comisso,criamosuma threadcomothread::spawn e enviamoso valordepipelo tx.send(3.1415f32).unwrap(); , para ser recebido pormeiodoreceiverletpi=rx.recv().unwrap();.

usestd::thread;usestd::sync::mpsc::channel;usestd::sync::mpsc::{Sender,Receiver};

fnmain(){let(tx,rx):(Sender<f32>,Receiver<f32>)=channel();thread::spawn(move||{tx.send(3.1415f32).unwrap();});letpi=rx.recv().unwrap();println!("Piis{:?}",pi);}

12.2ENVIANDOERECEBENDODADOS

12.2ENVIANDOERECEBENDODADOS 97

Page 111: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Outraformainteressantedeseescrevertxédefinindoaação.Nestecaso,ounwrapésubstituídopor.ok().expect("Pinãopodeserenviado").

tx.send(3.1415f32).ok().expect("Pinãopodeserenviado");

OSend()éexecutadopelathreadfilha,criandoumafilademensagens(nonossocaso,3.1415f32)nochannel, semcausarbloqueios. O recv() é feito pela thread mãe, que pega amensagem do canal e bloqueia a thread se não há mensagensdisponíveis.

Para fazer isso de forma não bloqueante, é possível usar otry_recv().Ambasasoperaçõessend()erecv()retornamumtipoResult.EmcasodeErr,ocanalnãofuncionamais.

Umpadrãointeressanteparautilizaréoapresentadoaseguir,emqueseretornaotipoReceiver<T>apartirdafunçãoquecriaoscanais:

usestd::sync::mpsc::channel;usestd::sync::mpsc::Receiver;

fncria_canal(dado:i32)->Receiver<i32>{let(tx,rx)=channel();tx.send(dado).ok().expect("Dadonaopodeserenviado");rx}

fnmain(){letrx=cria_canal(8000);ifletSome(msg)=rx.recv().ok(){println!("OkideGokuémaisde{:?}!!!",msg);}}

12.3COMOFUNCIONA?

98 12.3COMOFUNCIONA?

Page 112: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

O resultado disso exibe na telaO ki de Goku é mais de8000!!!.Mas omais importante neste exemplo é que a funçãocria_canal pode ser testada, algoque até entãonão tínhamosvisto.

#[cfg(test)]modtests{usesuper::*;

#[test]fnverifica_func_cria_canal(){letexpected=Some(8000);letrx=cria_canal(8000);assert_eq!(expected,rx.recv().ok());}}

Vale lembrar de que o método ok() nos retorna umOption,enãoumtipoqualquer,porissooexpecteddeveser Some(x) . Agora precisamos entender uma última etapa dacomunicaçãoentrethreads.

Otipodecomunicaçãoqueusamosatéagoraéassíncrona,ouseja, não bloqueia o código sendo executado. Porém, o Rusttambémpossuichannelssíncronos,chamadosdesync_channel,nosquaisosendbloqueiacasoseubufferfiquecheio,esperandoatéqueathreadmãecomeceareceberosdados.Segueoexemplo:

usestd::sync::mpsc::sync_channel;usestd::thread;

fnmain(){let(tx,rx)=sync_channel(1);

12.4 COMUNICAÇÃO ASSÍNCRONA ESÍNCRONA

12.4COMUNICAÇÃOASSÍNCRONAESÍNCRONA 99

Page 113: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

tx.send("Kakaroto!!").unwrap();thread::spawn(move||tx.send("Soumaisfortequevocê!").unwrap());

thread::sleep_ms(2000);

ifletSome(msg)=rx.recv().ok(){println!("Vegita:{:?}",msg);}ifletSome(msg)=rx.recv().ok(){println!("Vegita:{:?}",msg);}}

Oresultadoserá:

Vegita:"Kakaroto!!"Vegita:"Soumaisfortequevocê!"

Nesse código, temos algumas questões específicas, que creioserem importantes salientar. O valor passado como argumentopara sync_channel é chamado de bound, que representa otamanhodobuffer.Quandoessebufferficacheio,sendsfuturosserãobloqueadosesperandoobufferabrir.

Alémdisso, nossosend será recebido de forma sequencial,em que a thread terá um tempo de espera de 2000ms. A formacomo implementamos o recebimento das informações pode nãoser a ideal, mas mostra que conseguimos garantir que orecebimento de um buffer de tamanho 1 foi feito de formasíncrona.

100 12.4COMUNICAÇÃOASSÍNCRONAESÍNCRONA

Page 114: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

UmamençãohonrosaéaatualbibliotecaassíncronadoRust,a Tokio (https://tokio.rs/docs/getting-started/futures/).Entraremosemdetalhessobreissonoúltimocapítulo.

Agora chegou o momento de começarmos a aplicar oconhecimento gerado. Felizmente, a maior parte das bibliotecasquevamosanalisarjáfezaabstraçãodasthreadsparanós.

Para entendermos um pouco mais sobre como funcionaconcorrência emRust, faremos uma análise das bibliotecas Iron,Nickel,Hypere,porfim,aTokio.Elasimplementamfortementeasabstraçõesnecessáriasparagarantiraltaconcorrênciae facilidadedeimplementação.

12.4COMUNICAÇÃOASSÍNCRONAESÍNCRONA 101

Page 115: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

AplicandonossosconhecimentosBrincandocomNickelHyper:servidoresdesenvolvidosnomaisbaixonívelTokioeaassincronicidadeBibliografia

Aplicandonossosconhecimentos

Page 116: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO13

Percorremos um longo caminho, mas agora finalmenteteremosaoportunidadedeexplorartodoopotencialconcorrentede Rust. Não é necessário se aprofundarmuito na programaçãofuncional, pois não se trata de uma linguagem puramentefuncional, mas que apenas possui aspectos funcionais. Aquiabordaremos um problema que vai nos permitir explorarconcorrênciaeumpoucodessaprogramação.

NossoprojetosebasearáemumbancodedadossobreDragonBall,quepoderáoperarde formaconcorrente.Comoobancodedadosnãoéoobjetivonestemomento,vamoscontornarissocomoutrasformasmaisrudimentares.Nossospassosserão:

1. SubirumaaplicaçãoIronbaseadaemHelloWorld;2. Configurarthreadsetimeoutsparaonossoservidor;3. Aplicarconceitosbásicosderotas;4. ExplicarmaisafundooIron;5. PensaremtestesparaoIron;6. Criarumaaplicaçãowebbásica.

Para isso, vamos utilizar o repositório dbz-server

APLICANDONOSSOSCONHECIMENTOS

13APLICANDONOSSOSCONHECIMENTOS 103

Page 117: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

(https://github.com/naomijub/dbz-server) no GitHub, com oseguintecomando:

cargonewdbz-server--bin

Para este projeto, usarei a crate Iron(https://github.com/iron/iron),um frameworkwebmaismaduro,mais próximo de sua versão estável neste momento, com umabiblioteca específica para testes e bibliotecas robustas deroteamento, enconding de URLs, parser de parâmetros,persistência e logging. Para podermos utilizar o Iron em nossoprojeto,devemosadicionarsuacratenoarquivocargo.toml:

[dependencies.iron]version="*"

O Iron é um frameworkde servidor baseado emmiddlewarecomdesempenhorápidoeflexível,queforneceumabasepequena(mas robusta) para criar aplicativos complexos e RESTful APIs.Nenhum middleware é empacotado com o Iron; em vez disso,tudo édrag and drop (arraste e solte), permitindo configuraçõesridiculamentemodulares.Infelizmente,oIronnãopossuiumaboadocumentação, então, este capítulo pode ser uma boa fonte deinformações.

Neste momento, precisamos estabelecer qual será o passofundamental para iniciarmos nosso server. Para isso, proponhosubstituiroHelloWorldporOlaGoku!:

13.1IRON

OláGoku

104 13.1IRON

Page 118: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

externcrateiron;

useiron::prelude::*;useiron::status;

fnmain(){Iron::new(|_:&mutRequest|{Ok(Response::with((status::Ok,"OlaGoku!!!")))}).http("localhost:3000").unwrap();}

Obtemos via postman (ferramenta para fazer requests) umarespostaOlaGoku!!!aochamarmoslocalhost:3000:

Figura13.1:OláGoku

Falamosmuitodeconcorrência,maspoucomostramosissoatéagora. Felizmente, o Iron faz isso por baixo dos panos para nós,com auxílio do Hyper (https://github.com/hyperium/hyper), queveremos com mais profundidade nos próximos capítulos. Mascomo breve introdução, ele trata-se de uma biblioteca deimplementaçãoHTTPmodernaerápida,escritaemRust.

Queremos inicialmenteumservidorquepossautilizar todoo

Adicionandoconfigurações

13.1IRON 105

Page 119: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

poder computacional dos diversos cores da máquina. Para isso,podemos pressupor aproximadamente uma thread por core.Assim, para definirmos o número de threads do nosso servidor,basta adicionar a seguinte linha iron.threads =

NUMERO_THREADS;.

Pormeiode.timeouts, podemos definir casos de timeoutparanossoserviço,jáquenãoqueremosqueumathreadprendaocoreportempoindeterminado,ouqueremosqueorequestsejaretornadoem,nomáximo,umtempoespecífico:

externcrateiron;

usestd::time::Duration;//Novo

useiron::prelude::*;useiron::status;useiron::Timeouts;//Novo

fnmain(){letmutiron=Iron::new(|_:&mutRequest|{Ok(Response::with((status::Ok,"Ola!EusouoGoku!!!")))});iron.threads=8;iron.timeouts=Timeouts{keep_alive:Some(Duration::from_secs(10)),read:Some(Duration::from_secs(10)),write:Some(Duration::from_secs(10))};iron.http("localhost:3000").unwrap();}

No código anterior, fizemos duas modificações. Comiron.threads=8;, definimosqueonosso servidor atenderárequests em 8 threads diferentes e, pela struct Timeouts ,definimosostimeoutsdonossoservidorIron,iron.timeouts=Timeouts{...}.AstructTimeoutscontacom3camposcujosvaloressãoOption:keep_alive,readewrite.

106 13.1IRON

Page 120: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Mascreioqueestaimplementaçãopodeteralgunsproblemas.Primeiro, estamos definindo uma variável mutável no centro denosso servidor e alterando diversas vezes suas propriedades.Segundo,ocódigonãomeparecemuitolegívelelimpo.Oterceiro,e último ponto, é que a função http poderia ser chamadadiretamenteemIron.Umaabordagemmais"funcional"ecomummelhorestilodecódigoseriaaseguinte:

fnmain(){Iron{handler:|_:&mutRequest|{Ok(Response::with((status::Ok,"Ola!EusouoGoku!!!")))},threads:8,timeouts:Timeouts{keep_alive:Some(Duration::from_secs(10)),read:Some(Duration::from_secs(10)),write:Some(Duration::from_secs(10))}}.http("localhost:3000").unwrap();}

Ou, de forma mais limpa, poderíamos definir a funçãohandlereu_sou_gokuforadoescopodafunçãomain:

fnmain(){Iron{handler:eu_sou_goku,threads:8,timeouts:Timeouts{keep_alive:Some(Duration::from_secs(10)),read:Some(Duration::from_secs(10)),write:Some(Duration::from_secs(10))}}.http("localhost:3000").unwrap();}

fneu_sou_goku(_:&mutRequest)->IronResult<Response>{Ok(Response::with((status::Ok,"Oi,eusouoGoku!")))}

Claramente,nossaestruturadedadosdefineoservidor,emqueo handler recebe uma closure, e logo invocamos a função

13.1IRON 107

Page 121: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

http/unwrap.Outrapossibilidadequetemoséretornarocorpodorequest. Por exemplo,no código a seguir, obteremosOláGokuseenviarmosamesmafrase:

usestd::io::Read;

...

fnmain(){Iron{handler:|request:&mutRequest|{letmutbody=Vec::new();request.body.read_to_end(&mutbody).map_err(|e|IronError::new(e,(status::InternalServerError,"Erroraolerorequest")))?;Ok(Response::with((status::Ok,body)))},threads:8,timeouts:Timeouts{keep_alive:Some(Duration::from_secs(10)),read:Some(Duration::from_secs(10)),write:Some(Duration::from_secs(10))}}.http("localhost:3000").unwrap();}

108 13.1IRON

Page 122: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Figura13.2:RespostaOláGoku

Dificilmenteexisteumservidorquepossuiumúnicoendpoint.Para podermos disponibilizar diferentes endpoints em nossoservidor,devemosaplicarestratégiasderoteamentodechamadas,assim,vamosapresentarumpoucoderoutingcomIron.

Paratermosacessoàbibliotecaderouting,éprecisoadicionarumanovacrate,arouter,aocargo.toml:

[dependencies]iron="*"router="*"

13.1IRON 109

Page 123: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

No nosso main.rs , devemos adicionar as seguintesinformações:

externcraterouter;...userouter::Router;

Aplicando as duas funções que apresentamos anteriormente,emumGETeumPOST,obtemos:

userouter::Router;

fnmain(){letmutrouter=Router::new();router.get("/",eu_sou_goku,"index").post("/",retrucar,"post");

Iron{handler:router,threads:8,timeouts:Timeouts{keep_alive:Some(Duration::from_secs(10)),read:Some(Duration::from_secs(10)),write:Some(Duration::from_secs(10))}}.http("localhost:3000").unwrap();}

fneu_sou_goku(_:&mutRequest)->IronResult<Response>{Ok(Response::with((status::Ok,"Oi,eusouoGoku!")))}

fnretrucar(request:&mutRequest)->IronResult<Response>{letmutbody=Vec::new();request.body.read_to_end(&mutbody).map_err(|e|IronError::new(e,(status::InternalServerError,"Erroraolerorequest")))?;Ok(Response::with((status::Ok,body)))}

Vejaqueamodificaçãoocorreunasprimeiraslinhasdecódigo

110 13.1IRON

Page 124: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

comaadiçãodeumRoutereasdefiniçõesdecaminhosGETePOST:

letmutrouter=Router::new();router.get("/",eu_sou_goku,"index").post("/",retrucar,"post");

Iron{handler:router,...

Muitas vezes precisamos que nosso GET receba parâmetroscomo nomes, datas, rotas e conteúdos. Para podermos passaralgumparâmetroparaoGET,bastaadicionarumaespecificaçãode parâmetro em sua rota, como a linha .get("/:query",ola_vc, "query"), e implementar a função ola_vc, que seresponsabilizaráportrataresseparâmetro.

fnola_vc(request:&mutRequest)->IronResult<Response>{letrefquery=request.extensions.get::<Router>().unwrap().find("query").unwrap_or("/");Ok(Response::with((status::Ok,*query)))}

Oquery é oparâmetropassadopara aRouter::get. Eleprocurapelatagqueryeéonomedoparâmetroquedefinimosna URL. Como não queremos tomar ownership do request ,temosdepassá-loparaafunçãoola_vcpormeiodeumborrow&mutRequest.

Devidoàformacomopassamosorequest,precisamoslidarcomseusvaloresutilizandoconceitosde referência e aplicandoapalavraref,diferentedoqueoResponse::withespera.Assim,ao passar o resultado de query para response , vamosdereferenciarutilizando*(asterisco)parapassarmosumvalordotipo&str.Mas,esequisermosusaroqueryparamontaruma

13.1IRON 111

Page 125: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

frase?

fnola_vc(request:&mutRequest)->IronResult<Response>{letrefquery=request.extensions.get::<Router>().unwrap().find("query").unwrap_or("/");

Ok(Response::with((status::Ok,textinho(query))))}

fntextinho(query:&str)->String{letmutola_string:String="Olá".to_owned();leteu_sou:&str="\nEusouGoku!";

ola_string.push_str(query);ola_string.push_str(eu_sou);ola_string}

Nossoresultadoserá:

Figura13.3:OláVegita

Agora que exploramos melhor algumas das qualidades eopçõesqueoIronnosdá,podemosfazerumapausaparaanalisarmelhoroquetemosatéagora.Vamosdissecaroprimeirocódigo,

13.2MAISDETALHESDOIRON

112 13.2MAISDETALHESDOIRON

Page 126: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

pois passamos muito rápido por diversos pontos, como osmódulosdisponibilizados:

externcrateiron;

useiron::prelude::*;useiron::status;

fnmain(){Iron::new(|_:&mutRequest|{Ok(Response::with((status::Ok,"OlaGoku!!!")))}).http("localhost:3000").unwrap();}

Surpreendentemente,existeummontedepontosparadissecaremumtrechotãocurtodecódigo.OIronécompostoporvárioscomponentesmodularesetemumdesignmuitoextensível.Assim,seu código resultante é muitas vezes denso e bastante flexível,proporcionandomuitosignificadosemântico,compoucasintaxe.

Considerandosomenteas3primeiraslinhasdocódigo,temosasseguintesinformações:

AcrateIronfoiinvocadaparauso.

Ela disponibilizou todos ( prelude::* ) os símbolosdisponíveisnomóduloprelude.

E disponibilizou o módulo status, (status::Ok, "Ola

Goku!!!").OmóduloprelúdiodisponibilizadiversasstructsqueforamusadascomoRequest,ResponseeIron.

Ironéastructbasedomóduloe,talvez,detodalib,poiselaimplementaumhandlergenérico<H>quegerenciaosrequestsdosclientes,defineonúmerode threadscom

13.2MAISDETALHESDOIRON 113

Page 127: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

um usize e usa a struct Timeouts para definir ostimeoutsdoservidor.

A implementaçãodométodonew somente necessita deumhandler do tipoH paranos retornarum servidorbase.Suadefiniçãoédadaporfnnew(handler:H)->Iron<H> , cujo número default de threads é 8 *

num_cpus(númerodeCPUs).

Response é a struct responsável por criar a entidadederesposta do servidor. Ela possui 4 campos: status:Option<Status>, headers: Headers , extensions:TypeMap , body: Option<Box<WriteBody>> , sendorespectivamente o status da resposta (um enum comdiversos tipos, como status::Ok oustatus::NotFound); a struct responsável pelos headersda resposta (definida comodata:VecMap<HeaderName,Item>);extensivos,queéummapachaveadoportipos;ebody,quecorrespondeaocorpodamensagemenviada.

Request é a struct responsável por receber a requisiçãofeitapelocliente.Éconstituídapordiversoscampos,comoosdaResponse, epormaisalgunsdegerenciamentodesocket e URL, além do campo methods, definido pelostipos constituintes da RFC 7231(https://tools.ietf.org/html/rfc7231).

Asegundapartedeinteressenocódigoé:

fnmain(){Iron::new(/*...*/).http("localhost:3000").unwrap();}

Essa parte é responsável por inicializar o servidor Iron.

114 13.2MAISDETALHESDOIRON

Page 128: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Podemos ver que estamos ouvindo o protocolo HTTP emlocalhostnaporta3000,em.http("localhost:3000").Ométodo http do Iron aceita qualquer coisa que possa seranalisada por um std::net::SocketAddr. Mas a parte maisinteressanteéentenderoIron::New():

Iron::new(|_:&mutRequest|{Ok(Response::with((status::Ok,"OlaGoku!!!")))})

Vamoscomeçarolhandoaassinaturadométodonew.Comojá explicamos anteriormente, ele recebe umhandler e retornaumservidorIroncomaimplementaçãodoHandlerdetipoH:

impl<H>Iron<H>whereH:Handler{pubfnnew(handler:H)->Iron<H>;}

EdelasurgeumatraitespecialchamadaHandler:

pubtraitHandler:Send+Sync+Any{fnhandle(&self,&mutRequest)->IronResult<Response>;}

Os Handlers são os componentes centrais do Iron, eresponsáveisporreceberumrequestegerarumaresponse.Évalido fazer uma comparação com os controllers do frameworkMVC, porém, os handlers possuem muito menos limitações decomo podem ser usados, especialmente por poderem carregarpartedoqueseriao"controller"aoservidor.

AinstânciadotipoHandler,passadaparaoIron::new(),será a principal forma demanipular o nosso servidor.No nossocaso,oHandlerpassadoéumaclosure(lembrandodequeotipodoretornonãoénecessário):

|_:&mutRequest|->IronResult<Response>{

13.2MAISDETALHESDOIRON 115

Page 129: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Ok(Response::with((status::Ok,"OlaGoku!!!")))}

Mas aparte queparece sedestacar é aResponse::with().ElanaverdadeconstróiaResponsequeenviaremosdevoltaaocliente.Observesuaimplementação:

pubfnwith<M:Modifier<Response>>(modifier:M)->Response{Response::new().set(modifier)}

DefinitivamenteaquantidadedeModifierchamaaatenção,porissovamosveroModifier<Response>commaiscalma.

MODIFIERS

Osmodifiers (oumodificadores)sãoumdosprincipais tiposde extensão no Iron. Eles permitem que você defina novasmaneiras de fazer alterações em outros tipos. No Iron, ostipos de request e response são aqueles que maisaplicamos. Para entender como os modifiers funcionam,vamosverificarasuatrait:

pubtraitModifier<T>{fnmodify(self,&mutT);}

Bastantesimples,né?Tudoqueummodifier fazémodificaralgo(T).Contudo,acratemodifiernostrazoutratraitinteressante,aSet:

pubtraitSet{fnset<M:Modifier<Self>>(mutself,modifier:M)->SelfwhereSelf:Sized{modifier.modify(&mutself);self}

116 13.2MAISDETALHESDOIRON

Page 130: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

fnset_mut<M:Modifier<Self>>(&mutself,modifier:M)->&mutSelf{modifier.modify(self);self}}

NoIron, tantorequest quantoresponse implementamSet. Isso permite que modifiers sejam encadeados deforma bastante simples. Se voltarmos à implementação doResponse::with, veremos que tudo o que ela faz é criarumaresponsevaziaapartirdo::new,paraentãoaplicarummodifier,Set.

Agora que entendemosmelhor o funcionamento deles e dasprincipaisstructsdoIron,podemosresumirtudocompoucomaisde um tweet: para criar um servidor, basta usar Iron::new ,passarumhandler (nonosso caso, uma closure), e começar aescutaraporta.NossohandlerretornaráumanovaResponsecomocampodestatussetadocomostatus::Ok(200),ecomocorpoquedefinimosnorestoda tupla. Isso tudoacontecerápelaaplicaçãodomodifier.

Caso você precise que o modifier seja aplicado em um tipodiferentedoquejáfoiimplementado,énecessárioimplementaratraitparaaqueletipo.Vejaasimplementaçõesdisponíveisem:https://github.com/iron/iron/blob/master/src/modifiers.rs.

Criar servidores é muito útil e interessante. Porém, sequisermoscolocarnossosserviçosemprodução,devemos terum

13.3IRONTESTES

13.3IRONTESTES 117

Page 131: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

conjunto de regras que nos permitirá garantir uma qualidade deentrega,tantoemníveldenegócioquantodecódigo.

Outro fator importante de testes é que eles garantem quefuturas modificações não causem impacto negativo em nossoserviço. Para isso, precisamos entender como podemos testar oIron.Iniciamosesteprocessoadicionandoacratedoiron-test:

[dependencies]iron="*"iron-test="*"

Primeiramente,devemoshabilitarosmódulosdoiron-test,incluindooexterncrateiron_test; e posteriormenteuseiron_test::{request, response}; . A partir disto, podemospensar em um primeiro módulo de testes, com um assertsimples, que se constitui de uma response gerada a partir deuma rota simulada aohandler (neste caso, oGokuHandler).Depoisdisso,extraímosarespostaparaumresultefazemosoassert_eq!paravalidarsearespostadesaídacorrespondeàqueesperávamos:

#[cfg(test)]modtests{usesuper::*;

#[test]fntest_goku_handler(){letresponse=...letresult_body=response::extract_body_to_bytes(response);

assert_eq!(result_body,b"Oi,eusouoGoku!!!");}}

Agoradevemospensarnoquequeremosqueaconteçacomum

118 13.3IRONTESTES

Page 132: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

request quando umhandler específico é chamado – nestecaso,retornarb"Oi,eusouoGoku!!!".

letresponse=request::get("http://localhost:3000/hello",Headers::new(),&GokuHandler).unwrap();

Para que o nosso teste passe, precisamos de um handlerchamadoGokuHandler,implementadodestaforma:

useiron::prelude::*;useiron::{Handler,status};#[allow(unused_imports)]useiron_test::{request,response};

structGokuHandler;

implHandlerforGokuHandler{fnhandle(&self,_:&mutRequest)->IronResult<Response>{Ok(Response::with((status::Ok,"Oi,eusouoGoku!!!")))}}

Paraque tudo funcione,basta adicionar a funçãomain. Elanosgarantiráqueoservidorironestarárodandoemumarquivobinário,eteremosostestesmarcadoscomo#[test]:

externcrateiron;externcrateiron_test;

useiron::prelude::*;useiron::{Handler,Headers,status};#[allow(unused_imports)]useiron_test::{request,response};

structGokuHandler;

implHandlerforGokuHandler{fnhandle(&self,_:&mutRequest)->IronResult<Response>{Ok(Response::with((status::Ok,"Oi,eusouoGoku!!!")))}}

13.3IRONTESTES 119

Page 133: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

fnmain(){Iron::new(GokuHandler).http("localhost:3000").unwrap();}

#[test]fntest_goku_handler(){letresponse=request::get("http://localhost:3000/hello",Headers::new(),&GokuHandler).unwrap();letresult_body=response::extract_body_to_bytes(response);

assert_eq!(result_body,b"Oi,eusouoGoku!!!");}

Agora voltando ao nosso exemploOlá Vegita, Eu sou oGoku!, digamosqueagoraqueremosquedoisparâmetros sejampassados, como nome (name) e ação (action ). Um testeapropriado para o nosso caso seria algo como o apresentado aseguir, no qual definimos uma variável header do tipo application/x-www-form-urlencoded para explicitar comovamos passar o conteúdo da URL e os parâmetros de chamadapelo"name=Goku&action=Morra".Comisso,nossoobjetivoétercomoretorno:b"Goku,Morra!".

[dependencies]iron="*"iron-test="*"urlencoded="*"

#[cfg(test)]modtest{useiron::Headers;useiron::headers::ContentType;

useiron_test::{request,response};

usesuper::SpeechHandler;

#[test]fntest_body(){

120 13.3IRONTESTES

Page 134: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

letmutheaders=Headers::new();headers.set(ContentType("application/x-www-form-urlencoded".parse().unwrap()));letresponse=request::post("http://localhost:3000/",headers,"name=Goku&action=Morra",&SpeechHandler);letresult=response::extract_body_to_string(response.unwrap());

assert_eq!(result,b"Goku,Morra!");}}

Umapossível soluçãoparaeste casoé a apresentadaa seguir,queextraidaURLosparâmetrosqueesperamos.Casoalgumerroaconteça,umpanic!égeradocomamensagem"UrlEncondingisnotcorrect".

externcrateiron;externcrateiron_test;externcrateurlencoded;

useiron::{Handler,status};useiron::prelude::*;

useurlencoded::UrlEncodedBody;

structSpeechHandler;

implHandlerforSpeechHandler{fnhandle(&self,req:&mutRequest)->IronResult<Response>{letbody=req.get_ref::<UrlEncodedBody>().expect("UrlEncondingisnotcorrect");letname=body.get("name").unwrap()[0].to_owned();letaction=body.get("action").unwrap()[0].to_owned();

Ok(Response::with((status::Ok,name+","+&action+"!")))}}

OSpeechHandler define um parser com dois parâmetros,

13.3IRONTESTES 121

Page 135: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

nameeaction,noqualsomenteoprimeirodecadaésolicitadoeaownershipépassadaparaafunçãohandler.Estaretornauma&strdentrodeResponse.Aimplementaçãodeget_reféaseguinte:

fnget_ref<P>(&mutself)->Result<&<PasKey>::Value,<PasPlugin<Self>>::Error>whereP:Plugin<Self>,Self:Extensible,<PasKey>::Value:Any,{}

Aoaplicarmosessecódigoemmain,obtemos:

fnmain(){Iron::new(SpeechHandler).http("localhost:3000").unwrap();}

Figura13.4:Resultadodopost/urlencoded

122 13.3IRONTESTES

Page 136: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Como utilizamos o UrlEncodedBody , nosso handlerautomaticamente inferiu que estávamos usando um métodoPOST.Portanto,parautilizarométodoGET,teríamosdeusaroUrlEncodedQuerycomumaquery,comolocalhost:3000/?name=xxx&action=yyy.

Como já testamososhandlers,devemosnosaprofundarmaisemoutrostiposdeestruturasparatestar,comoosrouters, jáqueeles mesmos podem gerenciar alguns de nossos handlers. É umtestequasede integração, jáqueeles testamsuas funcionalidadescomvaloresdefácilcompreensão.

Aprimeiracoisaquequeremoséentenderoqueumtestederouter precisa fazer. Basicamente temos de garantir que umachamadaaumaURLespecíficadeveretornarumvalorespecífico.Paraesteexemplo,élegaltermosduasrotasGET,umaqueexibeoki e outra que responde alguma frase como nomeda pessoa.Assim,teremosasrotas:

GET/valor/:ki.GET/nome/:name.

Podemoscomeçartestandoarotaki.Comparando-acomostestes anteriores, a diferença é que estamos utilizando oapp_routeremvezdeumhandlerparatestar:

#[cfg(test)]modtest{useiron::Headers;

useiron_test::{request,response};

usesuper::{app_router};

#[test]

13.3IRONTESTES 123

Page 137: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

fntest_router(){letresponse=request::get("http://localhost:3000/valor/8000",Headers::new(),&app_router());letresult=response::extract_body_to_bytes(response.unwrap());

assert_eq!(result,b"Ehmaisde8000");}}

O que nos interessa nesse teste é principalmente o caminho/valor/8000 e o&app_router, pois é isso que nos permitetestar um router. Agora precisamos apresentar uma soluçãoparaapp_router.Elaconsistiráemretornaravariávelrouter,que foi criadapeloRouter::new, teveo caminhovalor/:kisetadoeseugerenciamentofeitopelohandlerKiHandler.

fnapp_router()->Router{letmutrouter=Router::new();router.get("valor/:ki",KiHandler,"router");router}

Vamos à implementação de KiHandler. Como precisamosdos parâmetros enviados (neste caso, :ki ), recuperamos osparâmetros do request com req.extensions.get::

<router::Router>(),eaplicamosafunção.find("ki") paraobter o valor associado a ki. No caso do teste anterior, será8000.

structKiHandler;

implHandlerforKiHandler{fnhandle(&self,req:&mutRequest)->IronResult<Response>{letparams=req.extensions.get::<router::Router>().expect("Routernãoencontradoapartirdaextensãod

124 13.3IRONTESTES

Page 138: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

oRequest.");letki=params.find("ki").unwrap();

Ok(Response::with((status::Ok,"Ehmaisde".to_string()+ki)))}}

Agoraprecisamosadicionaraoapp_routerumanovarota,a/nome/:name.Paraisso,escrevemososeguintetestequecumpreasexpectativassetadasnoiníciodotestederouter:

#[test]fntest_router_name(){letresponse=request::get("http://localhost:3000/nome/Vegita",Headers::new(),&app_router());letresult=response::extract_body_to_bytes(response.unwrap());

assert_eq!(result,b"Vegita\nEusouGoku!");}

Assim, é preciso implementar a nova rota ao routeradicionando: .get("nome/:name", NameHandler, "name

router").

fnapp_router()->Router{letmutrouter=Router::new();router.get("valor/:ki",KiHandler,"kirouter").get("nome/:name",NameHandler,"namerouter");router}

TambémprecisamoscriarohandlerNameHandler,quetemofuncionamentomuitosemalhanteaoKiHandler,masatuasobrea&strname:

implHandlerforNameHandler{fnhandle(&self,req:&mutRequest)->IronResult<Response>{

13.3IRONTESTES 125

Page 139: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

letparams=req.extensions.get::<router::Router>().expect("RouternãoencontradoapartirdaextensãodoRequest.");letname=params.find("name").unwrap().to_owned();

Ok(Response::with((status::Ok,name+"\nEusouGoku!")))}}

Oresultadodosnossostestesesuasimplementaçõesserá:

externcrateiron;externcrateiron_test;externcraterouter;

useiron::{Handler,status};useiron::prelude::*;

userouter::Router;

structKiHandler;structNameHandler;

implHandlerforKiHandler{fnhandle(&self,req:&mutRequest)->IronResult<Response>{letparams=req.extensions.get::<router::Router>().expect("RouternãoencontradoapartirdaextensãodoRequest.");letki=params.find("ki").unwrap();

Ok(Response::with((status::Ok,"Ehmaisde".to_string()+ki)))}}

implHandlerforNameHandler{fnhandle(&self,req:&mutRequest)->IronResult<Response>{letparams=req.extensions.get::<router::Router>().expect("RouternãoencontradoapartirdaextensãodoRequest.");letname=params.find("name").unwrap().to_owned();

126 13.3IRONTESTES

Page 140: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Ok(Response::with((status::Ok,name+"\nEusouGoku!")))}}

fnapp_router()->Router{letmutrouter=Router::new();router.get("valor/:ki",KiHandler,"kirouter").get("nome/:name",NameHandler,"namerouter");router}

fnmain(){Iron::new(app_router()).http("localhost:3000").unwrap();}

#[cfg(test)]modtest{useiron::Headers;

useiron_test::{request,response};

usesuper::{app_router};

#[test]fntest_router_ki(){letresponse=request::get("http://localhost:3000/valor/8000",Headers::new(),&app_router());letresult=response::extract_body_to_bytes(response.unwrap());

assert_eq!(result,b"Ehmaisde8000");}

#[test]fntest_router_name(){letresponse=request::get("http://localhost:3000/nome/Vegita",Headers::new(),&app_router());letresult=response::extract_body_to_bytes(response.unwrap());

13.3IRONTESTES 127

Page 141: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

assert_eq!(result,b"Vegita\nEusouGoku!");}}

AssimcomotodasbibliotecasRust,Ironencontra-seemestadode desenvolvimento. Felizmente, estes exemplos cobrem grandepartedosproblemasbásicosqueenfrentamosnodesenvolvimentode serviços, e suas implementações não devem variarsignificativamentequandoaversãoestávelforalcançada.

Nopróximocapítulo, veremosbrevemente comodesenvolverumpequenoservidorcomNickel,outrofamosoframework.

128 13.3IRONTESTES

Page 142: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO14

ONickeléumframeworkmuitoparecidocomoIron,porém,ele explora algumas formas diferentes de desenvolvimento. Umaspecto interessanteécomoeleexploramacrosealgunsaspectosfuncionais, como o conceito de que todas as funções devemretornarvaloresquepossamseroperadospornovasfunções.

Infelizmente, o pacotemínimo de Nickel possui centenas dedependências que não necessariamente serão usadas. E issoincorporamuitoruído.

Como dito no capítulo anterior, a documentação destesframeworks ainda émuito superficial. Com isso emmente, valefazer uma pequena demonstração do Nickel, começando com oclássicoHelloGoku!

Para isso, precisamos criar um novo projeto cargo eadicionaradependêncianickel:

$cargonewnickel-hello-goku--bin

[dependencies]nickel="*"

Agora temos de adicionar o código para o nosso HelloGoku!:

BRINCANDOCOMNICKEL

14BRINCANDOCOMNICKEL 129

Page 143: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

#[macro_use]externcratenickel;

usenickel::Nickel;

fnmain(){letmutserver=Nickel::new();

server.utilize(router!{get"**"=>|_req,_res|{"HelloGoku!"}});

server.listen("127.0.0.1:3000");}

Nossa primeira linha é um pouco diferente desta vez. Apresença do #[macro_use] é uma anotação para a cratenickel,poisutilizamosamacrorouter!.Depoisdeclaramososervidormutávelcomletmutserver=Nickel::new();.Noservidor, aplicamos a função utilize (explicada a seguir) efazemososervidorescutaremlocalhost:3000.

130 14BRINCANDOCOMNICKEL

Page 144: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

UTILIZE()

Essa função registra um handler de middleware, que seráinvocadoentreoutroshandlersdemiddleware,antesdecadaRequest.Omiddlewarepodeserempilhadoeéinvocadonamesmaordememquefoiregistrado.

Umhandlerdemiddlewareéquaseidênticoaumhandlerderoutes regular, com a única diferença de que ele espera umresultadodeActionoudeNickelError. Isso sedáparaindicaraoutroshandlersdemiddleware(seexistirem),maisabaixonapilha,quedevemcontinuar,ou sea invocaçãodomiddlewaredeveserinterrompidaapósohandleratual.

O próximo passo é entender como podemos criar diferentesrotas.Vamoscriarduas:umaquerespondeHelloGoku,eoutraquerespondeonomedousuário.

#[macro_use]externcratenickel;

usenickel::{Nickel,HttpRouter};

fnmain(){letmutserver=Nickel::new();

server.get("/hello",middleware!("HelloGoku")).get("/user/:usuario",middleware!{|request|format!("ola{:?}!",request.param("usuario").unwrap())});

14.1ROUTING

14.1ROUTING 131

Page 145: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

server.listen("127.0.0.1:3000");}

A únicamudança aqui é que substituímos o utilize pormétodosgetempilhadoscommiddlewaresparacontrolaremosrequestseresponses.

GrandepartedascomunicaçõesviaAPIsRestéfeitapormeiodeGETsePOSTs.JáfalamosbastantesobreGETsnocapítuloanterioreneste,masmuitosdosrequestsãofeitoscomPOSTs.

OPOSTéfeitogeralmenteemdoisformatos,XMLeJSON.Amaior parte das bibliotecasmaismodernas implementa primeirosoluções JSON(creioeu),porserumformatomaisclaro.Assim,queremosreceberumPOST de JSONpara retornaruma string,comalgumtextoespecífico.

Para isso, devemos adicionar a dependência rustc-

serialize="*"aoarquivotoml.

#[macro_use]externcratenickel;externcraterustc_serialize;

usenickel::{Nickel,HttpRouter,JsonBody};

#[derive(RustcDecodable,RustcEncodable)]structPessoa{nome:String,sobrenome:String,}

fnmain(){letmutserver=Nickel::new();

14.2LIDANDOCOMJSON

132 14.2LIDANDOCOMJSON

Page 146: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

server.post("/ola",middleware!{|request,response|letperson=request.json_as::<Pessoa>().unwrap();format!("Olasra.{},{}",person.sobrenome,person.nome)});

server.listen("127.0.0.1:3000");}

Aúnicanovidadeaquiéaformacomoorequestestásendolido,poistentamosentenderoJSONqueestásendopassadocomouma struct Pessoa . Isso se dá pela função .json_as::<Pessoa>().UmaoutravantagemdoNickeléafacilidadeparasetrabalharcomdadosemtemplates.

PelabreveexperiênciaquetivecomRails,erabastantesimplesgerartemplatesapartirdoservidor—algoquemuitosframeworksnão consideram uma prioridade, especialmente depois dosurgimento do React, que facilitou bastante esse serviço. Masapresentar templating do ponto de vista do Nickel pode serbastante interessante devido às características fortes deperformanceedeconcorrênciadoRust.Elasgarantemvelocidadederenderizaçãoefuncionamento.

Paracomeçarcomosistemadetemplates,podemoscriaralgobemsimplesesalvarcomotemplate.tpl,foradapastasrc/.Nossa ideia aqui ébem simples, gerarum templatequedizOlá{{Nomeparaexibir}}.

<html><body><h1>Olá{{nome}}!</h1></body>

14.3TEMPLATESEMNICKEL

14.3TEMPLATESEMNICKEL 133

Page 147: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

</html>

Isso deverá ser passado para a funçãoresponse.render(template,data):

#[macro_use]externcratenickel;

usestd::collections::HashMap;usenickel::{Nickel,HttpRouter};

fnmain(){letmutserver=Nickel::new();

server.get("/",middleware!{|_,response|letmutdata=HashMap::new();data.insert("nome","Julia");returnresponse.render("template.tpl",&data);});

server.listen("127.0.0.1:3000");}

Nestecaso,omiddlewarecriaumarelaçãodaHashMapentreovalorencontradonotemplateeovalorquedeveserinseridopelocódigo. A variável data recebe dois parâmetros na funçãoinsert(template_key,server_value): o primeiro é a chaveexistenteno templateque será substituídapelovalordo segundotemplate.

letmutdata=HashMap::new();data.insert("nome","Julia");

Esequiséssemosinserironomedousuáriononossotemplate?Teríamos de recebê-lo como parâmetro. Para isso, poderíamosusar o seguinte códigopara receber oparâmetrousuario pelarotaGETedevolvê-lonotemplate:

server.get("/:usuario",middleware!{|request,response|letmutdata=HashMap::new();

134 14.3TEMPLATESEMNICKEL

Page 148: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

data.insert("nome",request.param("usuario").unwrap());returnresponse.render("template.tpl",&data);});

Éinteressantedarumaolhadanamacromiddleware!, emhttp://nickel-org.github.io/nickel.rs/nickel/macro.middleware!.html. Ela éapresentada em routing, template e JSON, pois é uma dasferramentasmaisfortesdoNickel.

Essa macro nos permite definir em uma closure o que é orequesteoqueéoresponse,edefinirarelaçãoentreelespormeioderegrasmaislivres.

Comojáfalamos,IroneNickelsãoframeworksdiferentes,mascom aspectos importantes: a alta capacidade de concorrência doIron,eoaspectomaisfuncionaldeNickel.AgoraseguiremosparaumexemplocomHyper,quenostrazumagrandeliberdadeparaimplementaraspectosdeconcorrênciaefuncionais.

14.3TEMPLATESEMNICKEL 135

Page 149: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO15

Hyper é uma biblioteca de implementaçãoHTTPmoderna erápida, escrita em Rust para o Rust. É uma linguagem de baixonível com abstração de tipos, segura sobre o HTTP. Ela provêimplementações para cliente e servidor, e permite desenvolveraplicaçõescomplexasemRust.

UmfatocuriososobreaHyperéqueelautilizaoasyncIO(sockets não bloqueantes) por meio das bibliotecas Tokio eFutures.Aideiaéqueabibliotecaestáavançandorapidamenteemdireçãoàsuaversão1.0.

OinteressantedoHyperéqueelenospermitiráexplorarbemalgunsconceitosdeconcorrência,mesmoquemuitosdelessejamnativosaopróprioHyper.Agora,sabendodoquesetratamnossosexperimentos,vamoscomeçarcomumHelloWorld:

[dependencies]hyper="*"service-fn={git="https://github.com/tokio-rs/service-fn"}

externcratehyper;externcrateservice_fn;

HYPER:SERVIDORESDESENVOLVIDOSNOMAISBAIXONÍVEL

136 15HYPER:SERVIDORESDESENVOLVIDOSNOMAISBAIXONÍVEL

Page 150: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

usehyper::header::{ContentLength,ContentType};usehyper::server::{Http,Response};useservice_fn::service_fn;

staticTEXTO:&'staticstr="Olá,MundodoHyper!";

fnrun()->Result<(),hyper::Error>{letaddr=([127,0,0,1],3000).into();

letola=||Ok(service_fn(|_req|{Ok(Response::<hyper::Body>::new().with_header(ContentLength(TEXTO.len()asu64)).with_header(ContentType::plaintext()).with_body(TEXT))}));

letserver=Http::new().bind(&addr,ola)?;server.run()}

fnmain(){run();}

Oquepodemosverdediferentenoarquivotoml?Dessavez,importamosumabibliotecaquenãoseencontranocrates.io.Inclusive,essesistemanospermitedefinirabranchquequeremosimportar,casonãosejaamaster,algocomo:service-fn={git= "https://github.com/tokio-rs/service-fn", branch =

"outra"}. É também a primeira vez que mostramos um valorglobal,TEXTO, cuja vida perdura durante todo o programaporcausado&'static.

Além disso, definimos nosso endereço ao usarmos umaestruturadedadosdo tipo tupla, comumarrayde inteiroseuminteiro.Outrofatointeressanteéadeclaraçãodotipoolacomouma função anônima que não recebe parâmetros,mas respondeumOKquerecebeoutrafunçãoanônima.

15HYPER:SERVIDORESDESENVOLVIDOSNOMAISBAIXONÍVEL 137

Page 151: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Por fim, criamos um servidor unindo a estrutura de dados ànossa funçãoola. Creio que este seja o desenvolvimentomaisfuncionalatéomomento.

AgorapodemosnosaprofundaremcomooHyperfazalgumascoisas, sem a abstração doservice-fn, pois assim poderemosentendercomoessabibliotecalidacomaconcorrência.

[dependencies]futures="0.1.14"hyper="0.11.2"

No nosso arquivo main, podemos adicionar as crates quevamosutilizar,aplicando:

externcratehyper;externcratefutures;

Nossoprimeiropassoéimplementaroserviço(service)queatenderá nosso request . Neste caso, nossa relação entrerequesteresponseserárepresentadapelastructOla.

Então,precisamosimplementarServiceparaastructOla,masissogeraumcertoboilerplateparadefinirtiposdeRequest,Responseeerro,comoaseguir:

usehyper::server::{Http,Request,Response,Service};//...structOla;

implServiceforOla{

typeRequest=Request;typeResponse=Response;

15.1 CRIANDO UM SERVIDOR HELLOWORLDMAISROBUSTO

138 15.1CRIANDOUMSERVIDORHELLOWORLDMAISROBUSTO

Page 152: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

typeError=hyper::Error;typeFuture=Box<Future<Item=Self::Response,Error=Self::Error>>;

//...}

NoteapresençadotipoFuture; issosedeveao fatodequeele representa uma promessa de resposta da chamada, que seráresolvidapelafunçãocall().Jáquefalamosdecall(),temosopróximopasso,queseráimplementaraprópriachamada.

usefutures::future::Future;

usehyper::header::ContentLength;usehyper::server::{Http,Request,Response,Service};

//...

structOla;

constFRASE:&'staticstr="OlaGoku!";

implServiceforOla{

//...

fncall(&self,_req:Request)->Self::Future{Box::new(futures::future::ok(Response::new().with_header(ContentLength(FRASE.len()asu64)).with_body(FRASE)))}}

A função call recebe um Request que não estamosutilizandonestemomento,eretornaumaFuturecomheadersebody associados à frase que definimos como "Ola Goku!". Avantagemdessemétodoaparecenalimpezadafunçãomain,queagoraficariaassim:

15.1CRIANDOUMSERVIDORHELLOWORLDMAISROBUSTO 139

Page 153: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

fnmain(){letaddr="127.0.0.1:3000".parse().unwrap();letservidor=Http::new().bind(&addr,||Ok(Ola)).unwrap();servidor.run().unwrap();}

Comoletaddrdefinindooendereçoeoletservidordefinindo o servidor, temos a inicialização do servidor comservidor.run().unwrap().

Dificilmente um servidor será como o que elaboramos, poisgeralmente queremos que ele responda contextos específicos aURLs e métodos específicos. O primeiro passo pode ser definirumarotapararetornarmosalgoemummétodoPOST,eumarotapararetornamosalgoemummétodoGET.

Para isso, precisamos adicionar doismódulos use hyper::{Method, StatusCode};, e fazer nosso serviço ter um poucomaisde sentido.Umaboa ideia seria chamarnossastruct deDbzServeremvezdeOla,jáqueonomeOlaébastantevagoeinapropriadoparaumnomedeservidor.

Assim,ocorpodonossométodocallemroutingseriacomoaseguir:

implServiceforDbzServer{

//Typesaqui

fncall(&self,req:Request)->Self::Future{letmutresponse=Response::new();

match(req.method(),req.path()){

15.2 FAZENDO NOSSO SERVIDORRESPONDERUMPOST

140 15.2FAZENDONOSSOSERVIDORRESPONDERUMPOST

Page 154: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

(&Method::Get,"/")=>{response.set_body(FRASE);},(&Method::Post,"/dbz")=>{//maisdetalhesnofuturo},_=>{response.set_status(StatusCode::NotFound);},};

Box::new(futures::future::ok(response))}}

Nossométodocall ficou bastante interessante agora, mastemos um ponto fraco, a declaração de response let mut

response = Response::new(); , por ser mutável. Paraentendermosoque aplicar emnossa variávelresponse, temosum match que analisa dois parâmetros: o método que foichamado,comreq.method(), eocaminhoque foiusado,comeq.path().

OprimeiroéumGETcomcaminho"/",queretornaafrase"OláGoku!", eo segundoéumPOST na rota"/dbz", queaindanãovimosemdetalhes.Jáoterceiroéqualqueroutrovalor,queretornaumcódigoStatusCode::NotFound(404NotFound).A resposta é então introduzida dentro do Box de futures,Box::new(...),eretornadacomoumfuture::ok.

O primeiro passo do nosso servidor, e o mais simples, seriasimplesmenteretornarocorpodorequest,algocomo:

(&Method::Post,"/dbz")=>{response.set_body(req.body());},

Digamos que nosso objetivo aqui seja fazer mais do que

15.2FAZENDONOSSOSERVIDORRESPONDERUMPOST 141

Page 155: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

simplesmenteretornarocorpodorequest.Agoraéomomentopara fazermos uma pequena demonstração de como aplicaralgumafunçãoespecíficaaonossorequest,enãonecessitamosdenadamuitocomplexo.

Nossameta é transformar as informações do request emUPPERCASE.Paraisso,devemosusarafunçãodemapsobrecadabytecorrespondenteàStringdorequest,comaclosure|byte|byte.to_ascii_uppercase().

Para fazer isso,precisamosde alguns imports extras, como: acratedegerenciamentodeAscii,ascratesdegerenciamentodeStreams em futures , e o componente Chunk da cratehyper:

usestd::ascii::AsciiExt;usefutures::Stream;usefutures::stream::Map;usehyper::Chunk;

Como temos novos imports, vamos analisá-los um poucomelhor. O Body implementa a trait Stream , derivada defutures, produzindoumamontoadodeChunk (pedaços).UmChunk é apenasum tipoderivadodoHyperque representaumconjunto de bytes, e é facilmente convertido em outros tipos decontainersdebytes.

Desta forma, podemos definir nossa função que transformatudoemletrasmaiúsculaspara_maiusculas:

fnpara_maiusculas(chunk:Chunk)->Chunk{letmaiusculas=chunk.iter().map(|byte|byte.to_ascii_uppercase()).collect::<Vec<u8>>();Chunk::from(maiusculas)}

142 15.2FAZENDONOSSOSERVIDORRESPONDERUMPOST

Page 156: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Ainda precisamos fazer mais algumas alterações comoatualizarotipoStreamquenossaresponse estáusando.Porpadrão,otipoéumhyper::Body,masoresponse podeusarqualquerStreamqueimplementeAsRef<[u8]>.

Assim, vamos utilizar o tipo type Response =

Response<Map<Body, fn(Chunk) -> Chunk>>;, que opera emcimadeumMap.Asmudançasficamdessaforma:

implServiceforDbzServer{//othertypesstaythesametypeResponse=Response<Map<Body,fn(Chunk)->Chunk>>;

fncall(&self,req:Request)->Self::Future{//...(&Method::Post,"/dbz")=>{response.set_body(req.body().map(para_maiusculasas_));},//...}

}

Oatualestadodocódigoé:

externcratehyper;externcratefutures;

usestd::ascii::AsciiExt;usefutures::Stream;usefutures::stream::Map;usefutures::future::Future;

usehyper::{Chunk,Body};usehyper::{Method,StatusCode};usehyper::server::{Http,Request,Response,Service};

fnmain(){letaddr="127.0.0.1:3000".parse().unwrap();letservidor=Http::new().bind(&addr,||Ok(DbzServer)).unwr

15.2FAZENDONOSSOSERVIDORRESPONDERUMPOST 143

Page 157: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

ap();servidor.run().unwrap();}

structDbzServer;

implServiceforDbzServer{

typeRequest=Request;typeResponse=Response<Map<Body,fn(Chunk)->Chunk>>;typeError=hyper::Error;typeFuture=Box<Future<Item=Self::Response,Error=Self::Error>>;

fncall(&self,req:Request)->Self::Future{letmutresponse=Response::new();

match(req.method(),req.path()){(&Method::Post,"/dbz")=>{response.set_body(req.body().map(para_maiusculasas_));},_=>{response.set_status(StatusCode::NotFound);},};

Box::new(futures::future::ok(response))}}

fnpara_maiusculas(chunk:Chunk)->Chunk{letmaiusculas=chunk.iter().map(|byte|byte.to_ascii_uppercase()).collect::<Vec<u8>>();Chunk::from(maiusculas)}

Nossoatualcódigoébastantesimples,mastemosumservidordefinido de forma imutável, o que deveria nos garantir certaestabilidade e previsibilidade ao longo do curso de ação. Temostambémumserviçoqueécapazdeaplicarfunçõescombinadase,

144 15.2FAZENDONOSSOSERVIDORRESPONDERUMPOST

Page 158: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

por fim,uma respostano formatodefuture, quenospermitenãobloquear as threads de serviço caso a thread estagne emumloop.

Assim, podemos concluir que o Hyper é uma bibliotecabastanteinteressante,aindamaisquandocombinadacomIron.Ouso de futures permite maior controle sobre a geração dethreads e garante uma boa integridade da performance emmomentosdeestresse.

JásabemoscomolidarcomHyper,ecomoservirinformações.Nossoobjetivoagoraéfazernossoservidorprocessarumagrandequantidadedeinformaçõeseservirdeacordocomoqueoclientequer.Faremosusodeestruturasdedados,macroseumframeworkGraphQL chamado Juniper. Infelizmente, Juniper ainda não foiestabilizado como o Hyper, mas é uma alternativa bastantepromissora e não acredito que virão mudanças significativas naformadepensaroserviço.Estaseçãofoiescritaposteriormenteaodesenvolvimento do serviçoGraphQL e pode ser encontrado norepositóriohttps://github.com/naomijub/mtgql/.

Oprimeirocommitésimples,econsistedosmesmospassosdeantes: cargo new mtgql --bin para gerar o código inicial,adicionaradependênciadoHypernocargo.toml eumcódigobaseparafazeroserviçoresponder.Conformefizemosantes:

//main.rs

15.3 UMA API GRAPHQL COM HYPER EJUNIPER

UmnovoservidorHyper

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 145

Page 159: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

externcratehyper;

usehyper::{Body,Response,Server};usehyper::rt::Future;usehyper::service::service_fn_ok;

staticTEXT:&str="pong";

fnmain(){letaddr=([127,0,0,1],3000).into();letping_svc=||{service_fn_ok(|_req|{Response::new(Body::from(TEXT))})};letserver=Server::bind(&addr).serve(ping_svc).map_err(|e|eprintln!("servererror:{}",e));hyper::rt::run(server);}

#cargo.toml[package]name="mtgql"version="0.1.0"authors=["JuliaNaomi"]

[dependencies]hyper="*"

AgoraprecisamosentendercomovamosprocessarumarquivoJSON com todas as informações. O arquivo utilizado pode serencontrado emhttps://github.com/naomijub/mtgql/blob/master/body.json/. EsteJSONpossuiinformaçõesdecardsdojogoMagicTheGathering.

Para trabalharmos com JSON, precisamos incorporar umanovabiblioteca,aserde(https://serde.rs/),eaboaevelhafutures:

[dependencies]hyper="*"serde="*"

146 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 160: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

serde_json="*"serde_derive="*"futures="*"

Abibliotecaserdeédivididaemumconjuntodebibliotecasquetrabalhamdeformacomplementar,massuaprincipalfunçãoéserializar e desserializardados.Oprimeiropassoque gostariadetratar é a conversão de um arquivo JSON em uma struct Rust,deixandoumpoucodeladooHyper.Paraisso,oprimeiropassoéincorporar as funcionalidades de serde que precisaremosconsumir:

#[macro_use]externcrateserde_derive;#[macro_use]externcrateserde_json;externcrateserde;

Quando escrevo código Rust que consome macros, sempredeixoascratesquepossuemaanotação#[macro_use] notopo, conforme o exemplo acima, para facilitar a leitura eentendimentodascratesutilizadas.

Agora precisamos de uma forma de ler este JSON etransformá-lo emuma estruturaRust. Para lermos, precisaremosincorporarduasfuncionalidadesdastdquenãovêmporpadrão,que estão relacionadas à leitura de arquivos. Para isso,adicionamos as linhas use std::fs::File; , use

std::io::prelude::*;eusestd::io::Result; logo abaixodas crates declaradas. A leitura do arquivo body.json (que seencontra na raiz do projeto) pode ser feita através da funçãoread_fake(deionomederead_fakedevidoàideiadeevoluirestemétodoparaumhyper::clientquefaçarequestàAPIdo

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 147

Page 161: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Magic):

//Result<String>correspondeastd::io::Result<String>fnread_fake()->Result<String>{letmutcontents=String::new();File::open("./body.json")?.read_to_string(&mutcontents)?;Ok(contents)}

A função read_fake vai nos retornar um enum Resultcom uma String contendo as informações presentes no arquivobody.json.A primeira linhada função é a declaraçãode umaStringmutável chamadacontents. A String deve sermutável,pois a linha seguinte altera o conteúdo de String adicionando oconteúdo do JSON. A segunda linha consiste de duas partes. Aprimeira é a leitura do arquivo ./body.json, que é feita pelométodoopendonamespacestd::fs::File.Asegundaparteconsiste na leitura do conteúdo de body.json para dentro decontents. Podemos ver a presença de dois ?, eles são umoperadorunárioqueéutilizadocomosufixoaumafunçãoparasubstituir a função de unwrap() . Veja um exemplo maisdetalhado a seguir, no qual representamos amesma função comunwrapecom?:

//Result<String>correspondeastd::io::Result<String>fnread_fake()->Result<String>{letmutcontents=String::new();File::open("./body.json").unwrap().read_to_string(&mutcontents).unwrap();Ok(contents)}

//AlinhaaseguiréumaformasimplificadadaformacommatchFile::create("foo.txt")?.write_all(b"OláMundo!")

//CasomenossimplesdeusomatchFile::create("foo.txt"){Ok(t)=>t.write_all(b"OláMundo!"),

148 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 162: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Err(e)=>returnErr(e.into()),}

Opróximopassoécriarmosafunçãofake_json,queretornauma struct Card ou um JSON, através do tiposerde_json::Value.Emumaprimeirainstância,queremosquea função retorne a struct Card e, para isso, a escrevemos daseguinteforma:

pubfnfake_json()->Card{letv:Card=from_str(read_fake().unwrap().as_str()).unwrap();v}

Podemosverquea função lêdeumstr, atravésda funçãofrom_str , que recebe como argumento a leitura gerada nafunção anterior, read_fake. Como read_fake retorna umaStringefrom_stresperaum&str, devemosutilizar a funçãoas_str()paraconverteraStringem&str.ov ao final é ovalorde retornodaCard.A structCard serámostrada logo.ParaconvertermosovalorderetornoemJSON,devemosutilizaramacrojson!(), incluindo macros da crate serde_json, #[macro_use]externcrateserde_json;.O valor de retornomudaparaumserde_json::Value:

useserde_json::Value;

pubfnfake_json()->Value{letv:Card=from_str(read_fake().unwrap().as_str()).unwrap();json!(v)}

Agora,parapodermosserializaredesserializarastructCardprecisamosdeduasanotaçõesdoserde_derive:aSerializeea Deserialize. Junto a isso, é sempre interessante termos a

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 149

Page 163: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

capacidade de entender e copiar nossa struct. Para isso,adicionamosasanotaçõesDebugeClone.AestruturadastructCard seassemelhaàestruturadoJSON,naqualoscamposquepodem ser nulos ou podem não existir devem aparecer comoOption<_>.

#[derive(Serialize,Deserialize,Debug,Clone)]structCard{cards:Vec<CardBody>,}

#[derive(Serialize,Deserialize,Debug,Clone)]structCardBody{name:String,mana_cost:Option<String>,cmc:i32,colors:Vec<String>,color_identity:Option<Vec<String>>,types:Vec<String>,subtypes:Option<Vec<String>>,rarity:String,set:String,set_name:Option<String>,text:String,artist:String,number:String,power:Option<String>,toughness:Option<String>,layout:String,multiverseid:i32,image_url:Option<String>,rulings:Option<Vec<Rulings>>,printings:Vec<String>,original_text:Option<String>,original_type:Option<String>,id:String}

#[derive(Serialize,Deserialize,Debug,Clone)]structRulings{date:String,

150 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 164: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

text:String,}

Agora, voltando um pouco a nos preocupar com o Hyper,precisamos que a função fake_json receba um Request eretorneumResponsequeoHyperpossaentender.IssopodeserfeitoadicionandoResponseeBodyàfunção:

usehyper::{Request,Body,Response};

fnfake_json(_req:Request<Body>)->Response<Body>{letv:Card=serde_json::from_str(read_fake().unwrap().as_str()).unwrap();Response::new(Body::from(json!(v.clone()).to_string()))}

Para servirmos esseResponse com o corpo que queremos,bastautilizarafunçãodoHyperservice_fn_ok:

usehyper::rt::Future;usehyper::{Server};usehyper::service::{service_fn_ok};

fnmain(){letaddr=([127,0,0,1],3000).into();

letping_svc=move||{service_fn_ok(fake_json)};

letserver=Server::bind(&addr).serve(ping_svc).map_err(|e|eprintln!("servererror:{}",e));

hyper::rt::run(server);}

Primeira coisa de que precisamos é adicionar a crate quedisponibiliza GraphQL em Rust. Ela é chamada de Juniper

DeixandonossoservidorresponderviaGraphQL

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 151

Page 165: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

(https://crates.io/crates/juniper).Alémdisso,precisamosadicionarsuaimplementaçãoparaHyper,juniper_hyper,emaisalgumasdependências,conformeaseguir:

[dependencies]hyper="*"serde="*"serde_json="*"serde_derive="*"gqlog="*"futures="*"futures-cpupool="*"juniper="*"juniper_hyper="*"pretty_env_logger="*"

Tendo disponíveis as informações fornecidas por nossocontexto (body.json), ou "contrato", no caso de uma API,podemoscriaro schemadaquerydoGraphQL,que representaoesquemaqueestarádisponívelparaconsultaviarequisição.Aolero arquivobody.json, percebemos que ele responde um JSONcomoparâmetro"cards",queserárepresentadopeloparâmetro cards: Vec<CardBody> , na struct Card . O parâmetrocards da struct Card é um vetor de "corpos" para cadacard,comodefinidosnastructCardBody.Comestaestruturaserializada,podemosfazeroGraphQLentenderoscamposqueelarepresenta, adicionando a declaração de uso do macro da crateJuniper,#[macro_use]externcratejuniper;,eadicionandoa derivação GraphQLObject às structs Card, CardBody eRulings:

#[derive(Serialize,Deserialize,Debug,Clone,GraphQLObject)]#[graphql(description="CardFields")]structCardBody{name:String,manaCost:Option<String>,...

152 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 166: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

AntesdeentrarmosnoHyper,podemosdefinirumaprimeiraquery para oGraphQL. Essa primeira query será bem simples, econsistirá de retornar todas as cards presentes e com todos oscamposserializados,conformeaimagemaseguir:

QueryallCards,comresultadosecamposdisponíveis

Figura15.1:QueryallCards,comresultadosecamposdisponíveis

ParaimplementarmosaqueryGraphQLprecisaremosdeumastruct Query e de implementar suas subqueries através damacro graphql_object! . Esta macro implementa a traitGraphQLType . Ao utilizá-la, em vez de implementar o tipo

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 153

Page 167: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

manualmente, obtemos segurança de tipos e reduzimosdeclarações repetitivas. Além disso, a macro nos dá o poder deautorresolver os campos definidos como field através da suafunçãointernaresolve_field.

A struct Query poderia ser uma struct com campos eestados, especialmente se fôssemos utilizar a funcionalidadedemutation. O exemplo a seguir demonstra como declararumastructdeQuerycomestadointerno:

//Exemplodadocumentação#[macro_use]externcratejuniper;structUser{id:String,name:String,group_ids:Vec<String>}graphql_object!(User:()|&self|{fieldid()->&String{&self.id}fieldname()->&String{&self.name}fieldmember_of_group(group_id:String)->bool{self.group_ids.iter().any(|gid|gid==&group_id)}});

Neste exemplo, a struct User possui 3 campos, e quandoacessamosasqueriesdeidoudename, vemos apalavraself antes, que retorna o estado contido na struct. Naúltimaquery,member_of_group,vemosseaStringrecebidaestá contida dentro do vetor iterávelself.group_ids.iter()atravésdafunçnaoany.

Como nossa Query não terá estados, não precisamos nos

154 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 168: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

preocuparcomcamposinternosdastruct.Assim,podemospartirdiretamenteparaaimplementaçãodaqueryallCards:

usejuniper::{FieldResult};

structQuery;

graphql_object!(Query:Card|&self|{fieldallCards(&executor)->FieldResult<Vec<CardBody>>{Ok(executor.context().cards.to_owned())}});

A primeira diferença que vemos é que no nosso exemplodefinimosotipodaQuerycomoCard.Asegundaéquenossasfunções recebem o argumento &executor , responsável poracessarocontextoqueserápassadoparaoGraphQl.Veremosissologoadiante.Agoravamosaoqueacontecenestaquery.

QuandoaquerycontendoallCardséfeita,entendemosqueotipodocontextoédotipoCard,queseráoconjuntodecamposque poderemos consultar no contexto do GraphQL. Quanto àresposta, imagine que não queremos que todo o contrato deCardBody seja serializado via GraphQL. Para isso, poderíamosdeclararumastructderetornoCardQL que contém somenteoscamposquequeremosresolver.Algonesteformato:

#[derive(Serialize,Deserialize,Debug,Clone,GraphQLObject)]#[graphql(description="CardQueryFields")]structCardQL{name:String,mana_cost:Option<String>,cmc:i32,colors:Vec<String>,types:Vec<String>,subtypes:Option<Vec<String>>,rarity:String,number:String,power:Option<String>,

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 155

Page 169: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

toughness:Option<String>,}

Nestecaso,CardQLconteriaoscampospossíveisdeconsultar.

Além disso, temos um tipo de resposta específico para ocampo, que foi declarado no use juniper::{FieldResult} .Este campo é basicamente uma struct em volta de um ResultcontendoumFieldError.Nocasodonossoexemplo,utilizamoso <Vec<CardBody>> como resposta, mas poderia ter sido o <Vec<CardQL>> , conforme discutimos anteriormente. Porúltimo,temosoOk(),queretornatodasascards.

Estamosfalandomuitodecontexto,masnãoexplicamosbemoqueéisso.Paraisso,voumostrarcomojuniper_hyperintegraJuniper com Hyper. Agora precisamos de algumas coisas mais,como o tamanho das threads de CPU disponíveis na pool quevamosutilizar.Precisamosdonódulodeorigem(root_node) edo contexto. Dentro de main adicionaremos alguns destesparâmetros,comooexemploaseguir:

usefutures_cpupool::CpuPool;usejuniper::RootNode;usejuniper::{EmptyMutation};usestd::sync::Arc;

fnmain(){pretty_env_logger::init();letpool=CpuPool::new(4);letaddr=([127,0,0,1],3000).into();

letroot_node=Arc::new(RootNode::new(Arc::new(Query),EmptyMutation::<Card>::new()));...

O valor pool consiste do número de threads que vamos

156 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 170: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

consumir e o root_node é do tipo atomic reference count econteráumRootNodecomaQueryeumaEmptyMutation,querepresenta umamutação vazia, pois nosso código não alterará oestado do contexto. Agora, antes de entrar no serviço Hyperpodemos detalhar o último parâmetro que usaremos paraconstruiroGraphQL,oContext, ou contexto.Nosso contextodo tipoQuery está definido comoCard. Então, devemos ler oarquivobody.json, como fazemos coma funçãofake_read,masvamosalterarafunçãofake_jsonparaalgoqueretorneumcontexto falso, e para que seu tipo seja Card, como a funçãofake_ctx:

fnfake_ctx()->Card{letv:Card=serde_json::from_str(read_fake().unwrap().as_str()).unwrap();

v}

Utilizamos o serde_json para serializar o resultado deread_fakeemumastructCard.AúltimaediçãoparatermosoGraphQL funcionandoé substituiro serviçoping_svc porumserviçoqueimplementeostiposdoGraphQL.Faremosissodentrodaclosureservicedeclaradanoblocoaseguir:

#[macro_use]externcrateserde_json;#[macro_use]externcrateserde_derive;#[macro_use]externcratejuniper;externcrateserde;externcratehyper;externcratejuniper_hyper;externcratefutures;externcratefutures_cpupool;externcratepretty_env_logger;

usefutures::future;

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 157

Page 171: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

usefutures_cpupool::CpuPool;usehyper::rt::{Future};usehyper::{Body,Method,Response,Server,StatusCode};usehyper::service::{service_fn};usejuniper::{FieldResult,EmptyMutation};usejuniper::RootNode;usestd::sync::Arc;

fnmain(){pretty_env_logger::init();letpool=CpuPool::new(4);letaddr=([127,0,0,1],3000).into();

letroot_node=Arc::new(RootNode::new(Arc::new(Query),EmptyMutation::<Card>::new()));

letservice=move||{...}

letserver=Server::bind(&addr).serve(service).map_err(|e|eprintln!("servererror:{}",e));

hyper::rt::run(server);}

Aimplementaçãodoserviceficaassim:

letservice=move||{letroot_node=root_node.clone();letpool=pool.clone();letctx=Arc::new(fake_ctx());service_fn(move|req|->Box<Future<Item=_,Error=_>+Send>{letroot_node=root_node.clone();letctx=ctx.clone();letpool=pool.clone();match(req.method(),req.uri().path()){(&Method::GET,"/")=>Box::new(juniper_hyper::graphiql("/graphql")),(&Method::GET,"/graphql")=>Box::new(juniper_hyper::graphql(pool,root_node,ctx,req)),(&Method::POST,"/graphql")=>{Box::new(juniper_hyper::graphql(pool,root_node,ctx,req))

158 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 172: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

}_=>{letmutresponse=Response::new(Body::empty());*response.status_mut()=StatusCode::NOT_FOUND;Box::new(future::ok(response))}}})};

Observeasduaspalavrasreservadasmove.Afunçãodemoveé absorver o contexto dos valores externos através de uma cópiaparadentrodeseucontextoetomandoownershipdacópia.Depoisdisso,percebaumagrandequantidadedeclone(),oquesedeveànossanecessidadedecriar instânciasnovasparaosvaloresquetemos,comoobjetivodeospassarmosparaoutrasfunções.Alémdisso,afunçãoservice_fn é responsável por criar este serviçoGraphQL,respondendoumaFuturedentrodeumaBox. EssaBoxécriadaatravésdeumpatternmatchingdedoisargumentospresentes na requisição |req| : req.method(),

req.uri().path() , o método da requisição e a URL darequisição, respectivamente. O padrão (MÉTODO, "URL")

funcionacomoumadesestruturaçãonoformatodeumatupla,naqualosdoisargumentosdevemsersatisfeitos.

Primeiramente, o que vamos fazer são as declarações dosmódulosnoarquivomain,logoapósasdeclaraçõesdascrateseacriação de um arquivo lib.rs , que conterá o contexto dosmódulos a serem utilizados. As anotações para consumo demacros devem ser declaradas na raiz domódulo,lib.rs, pelaanotação#[macro_use]. E osmódulospara seremconsumidosfora da lib devem ser declarados com a palavra-chave pub

Refatorandoparautilizarmódulos

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 159

Page 173: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

antes,conformeaseguir.

Declaraçãodosmóduloseconsumo:

modclient;modschema;

...useclient::fake_ctx;useschema::{Card,Query};

Lib.rs com as crates consumidas e módulos públicosdeclarados:

#[macro_use]externcrateserde_derive;#[macro_use]externcratejuniper;externcrateserde_json;externcrateserde;externcraterayon;

pubmodclient;pubmodschema;

O módulo client é basicamente igual a toda a parterelacionada ao consumo do arquivo body.json , e ficará daseguintemaneira:

usestd::fs::File;usestd::io::prelude::*;usestd::io::Result;useserde_json::from_str;usesuper::schema::Card;

fnread_fake()->Result<String>{letmutfile=File::open("./body.json")?;letmutcontents=String::new();file.read_to_string(&mutcontents)?;Ok(contents)}

pubfnfake_ctx()->Card{letv:Card=from_str(read_fake().unwrap().as_str()).unwrap(

160 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 174: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

);v}

Já para o módulo schema vamos extrair tudo o que serelacionava à parte de schemas e queries do GraphQL, masadicionarumanovaquery,quechamaremosdecard_by_name.Aestruturadomódulosemanovaqueryestáconformeaseguir:

usejuniper::{FieldResult};userayon::prelude::*;

pubstructQuery;

graphql_object!(Query:Card|&self|{fieldall_cards(&executor)->FieldResult<Vec<CardBody>>{Ok(executor.context().cards.to_owned())}

...});

#[derive(Serialize,Deserialize,Debug,Clone,GraphQLObject)]#[graphql(description="CardsVector")]pubstructCard{pubcards:Vec<CardBody>,}

#[derive(Serialize,Deserialize,Debug,Clone,GraphQLObject)]#[graphql(description="CardFields")]#[allow(non_snake_case)]pubstructCardBody{...}

#[derive(Serialize,Deserialize,Debug,Clone,GraphQLObject)]#[graphql(description="CardRulings")]structRulings{...}

No arquivo lib.rs, possuímos a declaração de uma crate

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 161

Page 175: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

através de extern crate rayon , e no módulo schemapossuímos a seguinte declaraçãouserayon::prelude::*;. Acrate Rayon serve para fazermos processamento em paralelo deadapterseconsumers.

Nestemomento,oqueprecisamossabersobreacrateRayonéque basta substituir o iterador comum por um iterador emparalelo.Essasubstituiçãojánospermitiráfazeroprocessamentoemparalelodosdadospassadosparaoiterador.Atabelaaseguirmostracomosãorenomeadosestesiteradores:

Sequencial Paralelo

.iter() .par_iter()

.iter_mut() .par_iter_mut()

.into_iter() .into_par_iter()

Apósdeclararmosos iteradoresemparalelo,podemosutilizarconsumers e adapters da mesma forma explicada no capítulo 8.Iterators,adapterseconsumers.Assim,agoraqueremosdeclararaquerycard_by_name, e pesquisar em todas as cards de formaparalela.Para isso,namacrographql_object! declaramosumnovofieldcomnomedecard_by_name:

graphql_object!(Query:Card|&self|{...

fieldcard_by_name(&executor,name:String)->FieldResult<Vec<CardBody>>{letcards=executor.context().cards.clone();Ok(cards.into_par_iter().filter(|card|card.name.contains(name.as_str())).collect::<Vec<CardBody>>())}});

Ofieldcard_by_name recebeumaStringname e extrai

162 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 176: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

doexecutorocontextodecards,retornandoumFieldResultcomtodasascardsquepossuemnonomeaStringpassada,name.Oprocessamentodistoéfeitoatravésdoiteradorinto_par_itere do adapterfilter, que verifica se o nome da card correntepossui a&strname, para depois consumir todas as cards queretornaramverdadeirodaiteração.

Rayon é uma crate cujo objetivo é transformar cálculossequenciais em cálculos paralelos. Para a utilizarmos, bastaadicioná-la ao cargo.toml , declará-la por meio do externcraterayon e consumi-lacomuserayon::prelude::*;.Aúnica mudança efetiva que será feita no seu código é ademonstradanatabeladeSequencialeParalelo,queacabamosdever.Rayonpossui algumas outras funções interessantes, como asda famíliadepar_sort, que fazemprocessamento emparalelodasmaisdiferentesformas:

letmutv=[-5,4,1,-3,2];

v.par_sort();assert_eq!(v,[-5,-3,1,2,4]);

E a função join , que, quando associada à structThreadPool, nospermite chamar várias closures emparalelo edefiniraquantidadedethreadsquevamosutilizar.Paraisso,bastafazer a seguinte declaração de pool : let pool =

rayon::ThreadPoolBuilder::new().num_threads(8).build().

unwrap(); . Com isso, criamos uma pool de 8 threads pararesolveras funçõesqueserãochamadasemparalelocomjoin.Noexemploaseguirdeclaramosafunçãofib,queretorna1ou0 para valores de n iguais a 1 ou 0. Caso os valores sejam

UmpoucosobreRayon

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 163

Page 177: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

maiores,disparaduasfunçõesfibdentroderayon::join.Oresultado destas funções é consumido através de umadesestruturação em tupla chamada de let (a, b) , que éretornadacomoumasoma.install()executaumaclosureemumadas threadsdeThreadPool. Alémdisso, quaisquer outrasoperações de rayon chamadas dentro de install() tambémserãoexecutadasnocontextodoThreadPool.

fnmain(){letpool=rayon::ThreadPoolBuilder::new().num_threads(8).build().unwrap();letn=pool.install(||fib(20));println!("{}",n);}

fnfib(n:usize)->usize{ifn==0||n==1{returnn;}let(a,b)=rayon::join(||fib(n-1),||fib(n-2));returna+b;}

A próxima etapa será criar mais um field na macrographql_object!. Ele será responsável por buscar cards quecontenham, ou uma cor demana específica, ou um conjunto decoresdemanas.Nossadeclaraçãodaqueryficaráassim:

fieldmana_type_cards(&executor,color:Option<String>,colors:Option<Vec<String>>)->FieldResult<Vec<CardBody>>{letcards=executor.context().cards.clone();...}

Patternmatching

PatternmatchingversusIfLet

164 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 178: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

ComorecebemosdoisargumentosdotipoOption,devemosatuardeformaaidentificarquaissãoassituaçõesprioritárias.Daforma como eu implementei, pensei primeiro na situação deencontrarumacorespecífica,poisacreditoquesejamaisrelevanteeversátil.Pararesolverisso,fizumpatternmatchingcomcolor:

fieldmana_type_cards(&executor,color:Option<String>,colors:Option<Vec<String>>)->FieldResult<Vec<CardBody>>{letcards=executor.context().cards.clone();matchcolor{Some(c)=>Ok(cards.into_par_iter().filter(|card|card.colors.contains(&c.to_owned())).collect::<Vec<CardBody>>()),None=>Ok(vec![]),}

}

Opadrão criado verificaprimeiro secolor é umOptioncontendo um valor Some e retorna um FieldResult queconsisteda iteraçãoemparalelodecards.O filtro verifica se ocampo colors de card possui a color definida comoargumento, retornando um vetor com todos os resultadospositivosdofilter.OcasodeNone retornaumvetor vazio.Mas e quanto ao argumento colors ? Não podemossimplesmente fazer um novo match depois do primeiro? Nãoseriapossível,poisseriaumcódigoinatingível,jáqueafunçãoestáretornandooresultadodomatch já existente.Para evitar isso epara termos um código atingível, precisamos inserir um novomatchdentrodacláusulaNone.Faremosissodaseguinteforma:

fieldmana_type_cards(&executor,color:Option<String>,colors:Option<Vec<String>>)->FieldResult<Vec<CardBody>>{letcards=executor.context().cards.clone();

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 165

Page 179: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

matchcolor{Some(c)=>Ok(cards.into_par_iter().filter(|card|card.colors.contains(&c.to_owned())).collect::<Vec<CardBody>>()),None=>matchcolors{Some(cs)=>Ok(cards.into_par_iter().filter(|card|cs.iter().fold(true,|value,x|value&&card.colors.contains(&x.to_owned()))).collect::<Vec<CardBody>>()),None=>Ok(vec![]),}}}

PodemosverqueoresultadovazioOk(vec![]) foi passadoparaoNoneinterno.Nocenárioatual,temososeguintefluxo:

1. Noprimeiromatch,casoumacordemanaúnica,color,resultenovalorSome(c),retornaremosumResultcomovetordecardsquecontenhamascorescolor.

2. CasocolorsejaumNone,entraremosemumnovomatchparacolors.

3. CasocolorsretorneSome(cs) retornaremosumResultcomovetordecardsquecontenhamas foresdefinidas emcolors.

4. CasoomatchdecolorssejaumNone,retornaremosumResultcomumvetorvazio.

Nossa estratégia para o caso 3, ou Some(cs), é aplicar umfilteremcadacarderetornarumvetorcomtodasascardsquepossuem as cores de colors . Para termos uma closure queretorna valores verdadeirosnofilter aplicamosumiter acsseguidoporumfold,fold(true,|value,x|value&&

166 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 180: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

card.colors.contains(&x.to_owned()))).

Estefold temum valor inicial,value==true, e a corcorrentedaiteração,x.Atravésdafunçãocontains,verifica-sese ela está presente nas cores de card,card.colors.contains(&x.to_owned()).Os resultadosdacorcorrente e devalue são testados em um and, ou && (elógico),eretornadosaofiltercomoumargumentobooleano.Agoravamosentendercomoa lógicadopatternmatching ficariacomiflets.

IfLet

Assimcomocomosmatchesanteriores,temosduassituaçõesdiferentes e um resultado padrão caso os dois matches falhem.Para isso, utilizaremos a estrutura if let Some(c) = colorseguido por else if let Some(cs) = colors seguido porelse.Noprimeiroiflet verificamos sec é, de fato, umvalorSomedecolor.Casoforverdadeiro,retornamosocaso1dopatternmatching.SecolornãoretornarSome(c),cairemosno else, que verifica se cs é um valor do tipo Some decolors, e caso isso seja verdadeiro retornamos o caso 3 dopattern matching. Se nenhum dos dois casos for verdadeiro,retornamos o caso else com Ok(vec![]). A estrutura é aseguinte:

fieldmana_type_cards(&executor,color:Option<String>,colors:Option<Vec<String>>)->FieldResult<Vec<CardBody>>{letcards=executor.context().cards.clone();ifletSome(c)=color{Ok(cards.into_par_iter().filter(|card|card.colors.contains(&c.to_owned())).collect::<Vec<CardBody>>())}elseifletSome(cs)=colors{

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 167

Page 181: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Ok(cards.into_par_iter().filter(|card|cs.iter().fold(true,|value,x|value&&card.colors.contains(&x.to_owned()))).collect::<Vec<CardBody>>())}else{Ok(vec![])}}

Aomeu ver, temos uma estrutura de pattern matching maisadequadacomifletelsedoquecommatch.

Játemosbastantesqueriesemgraphql_object!,mastemospoucocontrole casoalgodêerrado.Émuitocomumrecebermosvalores inválidos, que podem interromper o funcionamento daAPI,ouvaloresmaliciososqueservemparaexploraralgumafalhade segurança. Por isso, devemos nos preparar e criar estruturasmais controladas e seguras. Um exemplo disso seria criar aconstanteCOLORS,quecontémascorespossíveisparaasmanas.Para isso, basta fazermos a seguintedeclaração emschema.rs: const COLORS: [&str; 5] =

["White","Red","Black","Green","Blue"];. Agora podemosadicionarumifquetestaseoargumentocolorestácontidonaconstanteCOLORS,pormeiode:

fieldmana_type_cards(&executor,color:Option<String>,colors:Option<Vec<String>>)->FieldResult<Vec<CardBody>>{if!COLORS.contains(&color.clone().unwrap_or(String::from("WRONG")).as_str()){returnOk(vec![]);}...}

Pensandoemerroscontrolados

168 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 182: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Podemosverque,alémdotesteparaverseCOLORS contém&color,aoextrairmosovalordecolorcriamosumacláusulaqueretorna"WRONG" atravésdeunwrap_or, que representaocasodecolorserdotipoOption.None.

Agora,esedecidirmoscriaroutraqueryparatestarararidadedas cards? Podemos criar uma constante que contenha todas aspossíveis raridades, como const RARITY: [&str; 3] =

["Common","Uncommon","Rare"]; . Com ela criada, podemoscriarumnovofieldquebuscacardsporraridade,comaquerycards_by_rarity:

fieldcards_by_rarity(&executor,rarity:String)->FieldResult<Vec<CardBody>>{if!RARITY.contains(&rarity.as_str()){returnOk(vec![]);}...}

Paraovalorderetorno,precisamossomentedeumfiltroqueverificaseararidadedacardéigualaoargumentorarity:

fieldcards_by_rarity(&executor,rarity:String)->FieldResult<Vec<CardBody>>{if!RARITY.contains(&rarity.as_str()){returnOk(vec![]);}letcards=executor.context().cards.clone();Ok(cards.into_par_iter().filter(|card|card.rarity==rarity).collect::<Vec<CardBody>>())}

Nosso próximo passo é, em vez de retornar Ok(vec![]) ,retornarmosumerroparaoGraphQL,conformeoquepodemosvernaimagemaseguirparaaquerydescrita:

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 169

Page 183: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

{cardsByRarity(rarity:"Teste"){name}}

Erroparararidadenãoencontrada

Figura15.2:Erroparararidadenãoencontrada

Paraqueessafuncionalidadeseconcretize,precisamosmudaro tipo de resultado de FieldResult<Vec<CardBody>> paraResult<Vec<CardBody>,InputError>, em queInputErrorserá definido no módulo gqlerror . Para começarmos,

170 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 184: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

precisamos declarar o módulo e consumir o erro que estamoscriando:

//main.rs...modgqlerror;

...usesuper::gqlerror::{InputError};

Aindadevemosadicionaradeclaraçãodomódulonolib.rs:pub mod gqlerror;. Com isso pronto, podemos começar aentenderaimplementaçãodegqlerror.rs:

//gqlerror.rsusejuniper::{FieldError,IntoFieldError};

pubenumInputError{ColorValidationError,RarityValidationError,}

implIntoFieldErrorforInputError{fninto_field_error(self)->FieldError{matchself{InputError::ColorValidationError=>FieldError::new("Theinputcolorisnotapossiblevalue",graphql_value!({"type":"COLORNOTFOUND"}),),InputError::RarityValidationError=>FieldError::new("Theinputrarityisnotapossiblevalue",graphql_value!({"type":"RARITYNOTFOUND"}),),}}}

O primeiro a se fazer é consumir os tipos de dados que

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 171

Page 185: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

precisamosutilizarparacriarnossoserroscustomizadospormeiodeFieldError,querepresentaumtipoErrnocampofielddoGraphQL, e a trait IntoFieldError , que nos permiteimplementaroqueesteerroresponderáaoGraphQL.Alémdisso,declaramos o enum público InputError , que contém doisestados:ColorValidationErroreRarityValidationError.

Agora podemos partir para a implementação da traitIntoFieldError.Afunçãoquedevemosimplementarsechamainto_field_error,epossuiumtipoderetornoFieldError, fn into_field_error(self) -> FieldError . Com ummatch,podemosdeterminarqualotipodeerroqueobtivemosecriarumnovoFieldError:

FieldError::new("Theinputrarityisnotapossiblevalue",graphql_value!({"type":"RARITYNOTFOUND"}))

Por final, devemosmudar o valor de retorno contido dentrodosifsdevalidação:

graphql_object!(Query:Card|&self|{...fieldcards_by_rarity(&executor,rarity:String)->Result<Vec<CardBody>,InputError>{if!RARITY.contains(&rarity.as_str()){returnErr(InputError::RarityValidationError);}

...}

fieldmana_type_cards(&executor,color:Option<String>,colors:Option<Vec<String>>)->Result<Vec<CardBody>,InputError>{if!COLORS.contains(&color.clone().unwrap_or(String::from("WRONG")).as_str()){

172 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 186: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

returnErr(InputError::ColorValidationError);}...}})

O último caso que quero apresentar com este exemplo éutilizando um filter com contexto interno, algo como umalógica interna que vá além de uma closure simples, eposteriormenteaplicandoumpatternmatchingaele.Faremosissonoúltimofield,cards_by_power_and_toughness, donosso graphql_object! . Em um primeiro momento, precisamosretornar todas as cards que possuempoder (power) e resistência(toughness) maiores que as enviadas como argumento. Caso ovalordacardnãofaçasentido,comoemcardsdeterreno,quenãocostumam ter poder e resistência, retornamos -1, para que oresultadodalógica>=sejafalsoe,portanto,acardsejafiltradadaiteração.Vejaocódigoaseguir:

fieldcards_by_power_and_toughness(&executor,min_power:i32,min_toughness:i32)->Result<Vec<CardBody>,InputError>{letcards=executor.context().cards.clone();Ok(cards.into_par_iter().filter(|card|{letcard_power=card.power.clone();letcard_tough=card.toughness.clone();card_power.unwrap_or("-1".to_string()).parse::<i32>().unwrap_or(-1)>=min_power&&card_tough.unwrap_or("-1".to_string()).parse::<i32>().unwrap_or(-1)>=min_toughness}).collect::<Vec<CardBody>>())}

Oquetemosdedentrodofilter éumpoucodiferentede

Últimafunção

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 173

Page 187: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

umaclosure comum,pois atravésdousode chaves conseguimosincluirumcontexto.Digamosqueocódigoficariaextremamenteconfusosefossefeitodaseguintemaneira:

card.power.clone().unwrap_or("-1".to_string()).parse::<i32>().unwrap_or(-1)>=min_power&&card.toughness.clone().unwrap_or("-1".to_string()).parse::<i32>().unwrap_or(-1)>=min_toughness})

Assim, se extrairmos o contexto de card.

<atributo>.clone() para um valor como let

card_<atributo> = card.<atributo>.clone() , teremos umcontexto melhor para compararmos. Além disso, estamosutilizandodoisunwrap_or emcontextosdiferentes.Oprimeirose refere ao fatodequeo atributopodenão existir e precisamospassaralgumvalorcoerenteparaafunçãoparse::<i32>(), naqual encontramoso segundounwrap_or, que retornaumvalornegativo.Creioquesejamelhornãoretornarcardscasofalhealgo,doquedarumpanicdesconhecido,jáqueumpanic poderiainformaraousuáriodadossensíveis.

Nosso último passo agora é salvar esse valor para podermosaplicar mais um filtro adicional, como custo máximo de mana.Para isso, vamos salvar o valor deOk em um campo chamadocards . O custo de mana, max_mana_cost , será um valoropcional,conformeocódigoaseguir:

fieldcards_by_power_and_toughness(&executor,min_power:i32,min_toughness:i32,max_mana_cost:Option<i32>)->Result<Vec<CardBody>,InputError>{letcards=executor.context().cards.clone().into_par_iter().filter(|card|{letinner_power=card.power.clone();letinner_tough=card.toughness.clone();inner_power.unwrap_or("-1".to_string()).parse::<i32>(

174 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 188: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

).unwrap_or(-1)>=min_power&&inner_tough.unwrap_or("-1".to_string()).parse::<i32>().unwrap_or(-1)>=min_toughness}).collect::<Vec<CardBody>>();...}

Paraextrairmosovalordemax_mana_cost,vamosutilizaraestratégiadoiflet.Assim,nocasodeiflet retornarumvalorSome,aplicamosumnovofiltro;casocontrário,retornamosOk(cards)nacláusulaelse.Onovofiltroconsistebasicamenteem filtrar as cards que tenham um custo demanamaiores quemax_mana_cost:

fieldcards_by_power_and_toughness(&executor,min_power:i32,min_toughness:i32,,max_mana_cost:Option<i32>)->Result<Vec<CardBody>,InputError>{letcards=executor.context().cards.clone().into_par_iter().filter(|card|{letinner_power=card.power.clone();letinner_tough=card.toughness.clone();inner_power.unwrap_or("-1".to_string()).parse::<i32>().unwrap_or(-1)>=min_power&&inner_tough.unwrap_or("-1".to_string()).parse::<i32>().unwrap_or(-1)>=min_toughness}).collect::<Vec<CardBody>>();

ifletSome(cmc)=max_mana_cost{Ok(cards.into_par_iter().filter(|card|card.cmc<=max_mana_cost.unwrap()).collect::<Vec<CardBody>>())}else{Ok(cards)}}

Agora nosso servidor GraphQL retorna várias queriesinteressantescomdiferentesestratégiasdeProgramaçãoFuncionalpara servir os dados de cards de Magic. No próximo capítulo,

15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER 175

Page 189: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

apresentarei uma última implementação de um servidor Web,dessavezdemaisbaixonívelecommaiorênfasenasquestõesdeassincronicidade.

176 15.3UMAAPIGRAPHQLCOMHYPEREJUNIPER

Page 190: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO16

Resumidamente, Tokio é o ponto central para tudo que serelaciona com assincronicidade em Rust. O núcleo da Tokiobaseia-se emfutures que são a base Rust para a computaçãoassíncrona,eelanosdisponibilizadiversascratesparanosauxiliaremaspectosespecíficos.

ATokiopossuialgumascaracterísticasinteressantes:érápida,pois possui baixo custo para abstrações, o que permite umaperformance incrível; tem alta produtividade, pois é bastantetrivial de implementar; confiabilidade, já que garante altasegurançanousodethreads;eporúltimo,éescalável,conseguindosuportargrandepressãonogerenciamentodethreads.

AlgumasdasprincipaisbibliotecasassociadasàTokiosão:

tokio-proto:camadamaissimplesparacriarservidorese clientes. O trabalho é principalmente lidar com aserializaçãodemensagens,paraoprotocuidardoresto.Abiblioteca inclui uma grande variedade de sabores deprotocolos,comostreamingseprotocolosmultiplex.tokio-core: tem como foco escrever código assíncrono

TOKIOEAASSINCRONICIDADE

16TOKIOEAASSINCRONICIDADE 177

Page 191: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

de baixo nível, permitindo lidar diretamente com I/O deestruturas de dados e eventos, mas com uma formaergonômicade altonível para lidar comos códigos.Coreserá importante quando precisarmos de total controle dointerior do servidor ou do cliente. Felizmente, nossoexemplodetokio-proto é detalhado o suficiente paraabrangerotokio-core.futures:provêabstraçõescomofutures,streamsesinks,usadasportodaTokio.

MULTIPLEX

Uma conexão de socketmultiplex é aquela que permite quemuitos requests simultâneos sejam feitos, com asresponsesvoltandonaordememqueestãoprontasemvezde na ordem solicitada. A multiplexação permite que umservidor comece a processar request assim que ele forrecebido, e permite responder ao cliente assim que forprocessado.

Vamos começar com a Tokio usando a biblioteca padrão, aproto.Faremosoexemploclássicodeumservidorqueretornaoque foi passado. Para iniciar, rodamos o comando cargo newservidor-proto--bineadicionamosasseguintesdependênciasaocargo.toml,emqueusaremosversõesespecíficas:

[dependencies]

16.1TOKIO-PROTOETOKIO-CORE

178 16.1TOKIO-PROTOETOKIO-CORE

Page 192: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

bytes="0.4"futures="0.1"tokio-io="0.1"tokio-core="0.1"tokio-proto="0.1"tokio-service="0.1"

Alémdisso,precisamosdisponibilizarnossasdependênciasnomain.rs:

externcratebytes;externcratefutures;externcratetokio_io;externcratetokio_proto;externcratetokio_service;

Umservidorprotoéformadopor3partesprincipais:

1. Umtransport,responsávelpelaserializaçãodosrequestseresponsesparaosocket.

2. A especificação do protocolo, que elabora um codec cominformaçõesbásicassobreoprotocolo(comomultiplexedoustreaming).

3. Um serviço que é responsável por determinar como umaresponseégeradaparaumdeterminadorequest.Nestecaso,oserviçoéumafunçãoassíncrona.

Oprimeiropasso,eomaissimples,é implementarumcodecdelinhaqueincluiremosemumarquivolib.rs,comonomedepubmodcodec,eestaráarmazenadonoarquivocodec.rssobo nome depub struct LineCodec. É importante lembrar deincluiromódulodocodecnomain.rscommodcodec.

Para começar, devemos implementar as traits Encode e

Implementandoocodec

16.1TOKIO-PROTOETOKIO-CORE 179

Page 193: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Decode para nossa struct LineCodec . Assim, definimos asimplementaçõesdeDecoderedeEncoderdacratetokio_io,comasdefiniçõesdeItemeErro:

Codecs são programas responsáveis por codificar edecodificarstreamsdedados.

usestd::io;usestd::str;usetokio_io::codec::{Encoder,Decoder};

pubstructLineCodec;

implDecoderforLineCodec{typeItem=String;typeError=io::Error;

//...}

implEncoderforLineCodec{typeItem=String;typeError=io::Error;

//...}

Usaremos String para a representação das linhas, o quesignificaquenossoEncodereDecoderprecisarãoimplementaracodificaçãoUTF-8paraoprotocolodelinha.Aformamaisfácildesecomeçaréimplementandoafunçãoencode:

usebytes::BytesMut;

implEncoderforLineCodec{typeItem=String;typeError=io::Error;

180 16.1TOKIO-PROTOETOKIO-CORE

Page 194: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

fnencode(&mutself,msg:String,buf:&mutBytesMut)->io::Result<()>{buf.extend(msg.as_bytes());buf.extend(b"\n");Ok(())}}

O código é simples, mas precisa de algumas explicações. AprimeiradelaséoBytesMut,quenosprovêumaformasimples,maseficiente,degerenciarbuffers–algocomoumArc<[u8]>.Ométodoextendestendeumacoleçãodeacordocomotipoaseriterado (no nosso caso, bytes). O Ok(()) refere-se ao tipo deretornoio:Result<()>.Agoravamosimplementarodecode:

implDecoderforLineCodec{typeItem=String;typeError=io::Error;

fndecode(&mutself,buf:&mutBytesMut)->io::Result<Option<String>>{ifletSome(i)=buf.iter().position(|&b|b==b'\n'){letline=buf.split_to(i);

buf.split_to(1);

matchstr::from_utf8(&line){Ok(s)=>Ok(Some(s.to_string())),Err(_)=>Err(io::Error::new(io::ErrorKind::Other,"UTF-8invalido")),}}else{Ok(None)}}}

Apesardeumpoucoenrolada, esta soluçãododecode temum aspecto funcional interessante.Nosso valor de resposta pode

16.1TOKIO-PROTOETOKIO-CORE 181

Page 195: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

serresumidocomaseguintelinha:ifletSome(i)={...}else{Ok(None)}.

Ocasodoelseébastanteintuitivo,poisretornaum"nada".Já o Some(i) itera sobre o buffer de bytes para encontrar asposiçõesdeb'\n',buf.iter().position(|&b|b==b'\n').Nessasposições,quebramosasequênciaemduascomoletline= buf.split_to(i); , e retiramos o b'\n' aplicando obuf.split_to(1).

Após isso, retornamos um Ok para o caso de termosconseguido transformar nossos bytes em str , com matchstr::from_utf8(&line) { Ok(s) =>

Ok(Some(s.to_string())), ... . Agora vamos codificar edecodificarbytesemstringsUTF-8.Nossoresultadoé:

externcratetokio_io;externcratebytes;

usestd::io;usestd::str;useself::bytes::BytesMut;useself::tokio_io::codec::{Encoder,Decoder};

pubstructLineCodec;

implDecoderforLineCodec{typeItem=String;typeError=io::Error;

fndecode(&mutself,buf:&mutBytesMut)->io::Result<Option<String>>{ifletSome(i)=buf.iter().position(|&b|b==b'\n'){letline=buf.split_to(i);

buf.split_to(1);

matchstr::from_utf8(&line){Ok(s)=>Ok(Some(s.to_string())),

182 16.1TOKIO-PROTOETOKIO-CORE

Page 196: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Err(_)=>Err(io::Error::new(io::ErrorKind::Other,"UTF-8invalido")),}}else{Ok(None)}}}

implEncoderforLineCodec{typeItem=String;typeError=io::Error;

fnencode(&mutself,msg:String,buf:&mutBytesMut)->io::Result<()>{buf.extend(msg.as_bytes());buf.extend(b"\n");Ok(())}}

Nossopróximopassoétransformarocodecemumprotocoloreal. Felizmente, a crate tokio-proto já possui uma grandevariedade de estilos de protocolo, como o multiplexed e ostreaming.ParacontinuarmoscomoparadigmadoLineCodec,vamosdesenvolverumprotocolobaseadoemlinha,aplicandoumprotocolodepipelinesemstreaming.

VamoscriarumarquivoRustcomonomedeprotocol.rs.Noarquivolib,vamos incluirpubmodprotocol; e injetaresse módulo na main, com mod protocol. Para o tipo deprotocolo que escolhemos, devemos incluir use

tokio_proto::pipeline::ServerProto; e criar uma novastruct, já que não lidaremos com estados de configuração pubstructLineProto.

Implementandooprotocolo

16.1TOKIO-PROTOETOKIO-CORE 183

Page 197: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Nestemomento, temosde enfrentarumpoucodeboilerplateparadefinirmoscorretamenteoestilodeprotocolocomocodecdeformaassíncrona.Issoresultaráem:

externcratetokio_proto;externcratetokio_io;

useself::tokio_proto::pipeline::ServerProto;useself::tokio_io::{AsyncRead,AsyncWrite};useself::tokio_io::codec::Framed;usestd::io;usesuper::codec::LineCodec;

pubstructLineProto;

impl<T:AsyncRead+AsyncWrite+'static>ServerProto<T>forLineProto{typeRequest=String;typeResponse=String;typeTransport=Framed<T,LineCodec>;typeBindTransport=Result<Self::Transport,io::Error>;

fnbind_transport(&self,io:T)->Self::BindTransport{Ok(io.framed(LineCodec))}}

DefinimosumtipoTquedevepossuirastraitsAsyncReadeAsyncWrite,paraimplementarumServerProto(protocolodeserviço)paraanossastructLineProto.OtipodeRequestdeveserigualaotipodoItemdoDecoder, eo tipodeResponsedeveserigualaotipoItemdoEncoder.

Nosso tipo Transport gera um pouco de boilerplate parafazer liga entre nosso estilo de protocolo e nosso codec. E,finalmente,temosafunçãoquefazobinddotipoT,comasaídaBindTransportapartirdocodecLineCodec.

Implementandooserviço

184 16.1TOKIO-PROTOETOKIO-CORE

Page 198: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Agorageramosumprotocologenéricodelinha,masnãotemosmuito como utilizá-lo. Para isso, precisamos que ele sejagerenciado por um serviço que indicará como responder a umrequest.

Acratetokio_serviceprovêumatraitparaisso.Parausá-la,devemoscriarumarquivoservice.rs, incluirnoarquivolibpub mod service; , e disponibilizar nossa implementação deserviçonamaincommodservice.Para iniciarmosoarquivoservice.rs,vamosnecessitarinicialmentedotrechoaseguir:

externcratetokio_service;

useself::tokio_service::Service;

pubstructEchoService;

Comoprincípiocentral,umserviçodacratetokio_serviceé uma função assíncrona, não bloqueante, que transforma umrequestemumaresponse.AformacomoTokiosepropôsaresolver código assíncrono é pormeio dasfutures, vindas datrait Future . Elas são basicamente a versão assíncrona deResult–naverdade,umapromessadeResult.

Paraincluirasfutures,bastaadicionaroseguintecódigo:

externcratefutures;

useself::futures::{future,Future};

Queremosquenosso serviço retorneoque lhe foipassado,oqueusualmentechamamosdeecho.Paraisso,queremosqueessenosso serviço aplique a future::ok, pois isto produzirá umafuture que imediatamente retornará uma promessa de valor,nestecaso,retornandoorequestcomoresponse.

16.1TOKIO-PROTOETOKIO-CORE 185

Page 199: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Para manter isso mais simples, criaremos uma box quetransformará a future em uma trait, o que vai nos permitiráutilizaratraitparadefinirnossoserviço.Issoresultaráem:

externcratetokio_service;externcratefutures;

useself::tokio_service::Service;useself::futures::{future,Future};usestd::io;

pubstructEchoService;

implServiceforEchoService{typeRequest=String;typeResponse=String;typeError=io::Error;typeFuture=Box<Future<Item=Self::Response,Error=Self::Error>>;

fncall(&self,req:Self::Request)->Self::Future{Box::new(future::ok(req))}}

OstiposdeRequesteResponse devem ser iguais aosdoprotocolo.OtipoErrorparaprotocolossemstreamingdeveserio::Error,jáotipoFuturepossuiumaboxparasimplicidade,mas tem como objetivo gerar a response. A função call éresponsável por produzir uma future capaz de gerar umaresponse para um request , que se torna bastante trivialBox::new(future::ok(req))pelousodabox.

Finalmente,podemosvoltarparaamain e configurarnossoservidor para rodar. Temos um protocolo e um serviço sobre oqual proveremos, e a única coisa que nos falta é configurar o

Rodandonossoservidor

186 16.1TOKIO-PROTOETOKIO-CORE

Page 200: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

servidorpara rodá-lo.Faremos issousandoaTcpServer, bastaadicionarusetokio_proto::TcpServeràsuamain:

externcratetokio_proto;

useself::tokio_proto::TcpServer;

modcodec;modprotocol;modservice;

fnmain(){letaddr="0.0.0.0:12345".parse().unwrap();letservidor=TcpServer::new(protocol::LineProto,addr);

servidor.serve(||Ok(service::EchoService));}

Primeiro, importamos os módulos que definimosanteriormentecomasériedemod,depoisdefinimosumendereçolocalhostcomumaportaemaddr.Comisso,podemospassarpara nosso TcpServer o protocolo que vamos utilizar e oendereçoemqueeleseencontra.

Por fim,provemosumamaneirade instanciar o serviçoparacadanovaconexão.Paravero servidor funcionando,basta rodarcargo run e, em outra janela do terminal, rodar telnetlocalhost12345.Valeapenarodaremdiversosterminaisparatestar os resultados de assincronicidade. Ao final do capítulo,temososlinkscomoscódigosgerados.

Tokio existe para resolver problemas de I/O de formaassíncrona, mas o interessante é que a lib não exige que vocêpossuamuito conhecimento sobre I/O assíncrono. Um exemplo

16.2FUTURES

16.2FUTURES 187

Page 201: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

simples seria ler um número exato de bytes em um socket .Podemosler4096bytesparadentrodovetormy_vec.

socket.read_exact(&mutmy_vec[..4096]);

Como a biblioteca padrão é síncrona, a chamada a estesockettorna-sebloqueante,permanecendodormenteenquantomais bytes não são recebidos. Isso pode se tornar um problemapara contextos de servidores escalados, como servir a diferentesclientesdeformaconcorrente.

Paraisso,emvezdebloquearmosathreadcomorequestatéqueeleestejacompleto,podemosregistrá-locomoemandamentoe liberara threadparaatenderaoutrademanda,permitindoqueelavoltequandoorequestestiverprontoparaseratendido.Issosignifica que podemosusar umaúnica thread para gerenciar umnúmero arbitrário de conexões, com cada conexão usandorecursosmínimos.Énissoqueasfuturesbrilham.

Uma future é um valor que está no processo de sercomputada, mas talvez não esteja pronta para fazer o todo.Geralmente, ela está completa oudisponível devido aumeventoqueaconteceuemoutrolugar.Futurespodemrepresentarumasériedeeventos:

Uma query a um banco de dados – Quando a querytermina, afuture é tida como completa, e o valor é oresultadodaquery.UmainvocaçãoRPCaumservidor–Quandooservidorresponde,afuture se completae seuvaloréa respostadoservidor.Timeouts – Se o tempo terminar, a respostadafutureserá().

188 16.2FUTURES

Page 202: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Uma tarefa intensa de CPU de longa duração rodandoem um pool de threads – Quando a task terminar, afuturesecompletaeoresultadoéoretornodatarefa.Lendo bytes de um socket – Quando os bytes estãoterminados,afuture secompleta,eosbytespodemserretornadosdiretamente.

Resumidamente,asfutures sãoaplicadasa todosos tiposdeeventosassíncronos.Aassincronicidadereflete-seno fatodevocêreceberafutureinstantaneamente,sembloquearathread, apesar de o seu valor representar um que estádisponívelemalgummomentodotempo,ofuturo.

Nossoobjetivonesseexemploéadicionartimeoutsaumataskcustosaparaosistema:calcular seumnúmeroéprimode formaineficiente. Para isso, o primeiro passo é iniciar uma app comcargonew--bintimeout-primos&&cdtimeout-primos.

Vamoscomeçarcriandonossafunçãoburraqueverificaráseonúmero é primo percorrendo todos, de 2 até ele mesmo, everificando se sãodivisíveis (a ideia éque elanão sejaotimizadamesmo):

constPRIMO:u64=15485867;

fnmain(){ifeh_primo(PRIMO){println!("{}éprimo",PRIMO)}

Umexemplocomfutures

16.2FUTURES 189

Page 203: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

}

fneh_primo(numero:u64)->bool{foriin2..numero{ifnumero%i==0{returnfalse}}true}

Essaseriaaversãosíncronadonossoproblema,naqualathredmain fica bloqueada até que a função eh_primo retorne seuvalor.

Agora vamos utilizar pools de threads e futures pararesolveresseproblema.Então,vamosprecisardasseguintescrates:

[dependencies]futures="*"futures-cpupool="*"

Primeiramente,precisamosdisponibilizá-lasnonossocódigo,com:

externcratefutures;externcratefutures_cpupool;

usefutures::Future;usefutures_cpupool::CpuPool;

fnmain(){...}

Comisso,podemoscriarnossapooldethreadscomletpool= CpuPool::new_num_cpus() . Agora precisamos criar nossafunção que gera nossa avaliadora de primos e retorna umafuture,aletprime_future=pool.spawn_fn(||{...}).Dentrodafunçãopool.spawn_fn,temos:

16.3AVERSÃOASSÍNCRONA

190 16.3AVERSÃOASSÍNCRONA

Page 204: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

pool.spawn_fn(||{letprimo=eh_primo(PRIMO);

letres:Result<bool,()>=Ok(primo);res);

Verificamosseonúmeroéprimocomeh_primo(PRIMO) e,se for, precisamos encapsular emum tipoResult<bool,()>.Nossoresultadofinaléalgoassim:

externcratefutures;externcratefutures_cpupool;

usefutures::Future;usefutures_cpupool::CpuPool;

constPRIMO:u64=15485867;

fnmain(){letpool=CpuPool::new_num_cpus();

letfuture_primo=pool.spawn_fn(||{letprimo=eh_primo(PRIMO);

letres:Result<bool,()>=Ok(primo);res});println!("FutureCriada");

iffuture_primo.wait().unwrap(){println!("Primo");}else{println!("Composto");}}

fneh_primo(numero:u64)->bool{foriin2..numero{ifnumero%i==0{returnfalse}}true}

16.3AVERSÃOASSÍNCRONA 191

Page 205: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Utilizar wait não é algo muito comum, mas nos permiteentenderadiferençaentreafutureeovalorqueelaproduz.Ounwrapserveparagarantiraextraçãodovalordela.

Observando o exemplo anterior, podemos pensar quetomamos um caminho difícil para trabalhar com thread pools, eatémeio inútil. Porém, a graça aparece quando podemos limitaraindamaisascondiçõesdeassincronicidade.Énestemomentoquea força das futures aparece, por conta de sua habilidade decombinar resultados – o que demonstraremos com a adição detimeoutsaonossocódigo.

Isso nos permitirá perceber como as futures podem serescaláveis e combináveis de forma complexa, delegando àbiblioteca a responsabilidade de gerenciar todos os estados e assincronizações. Antes de iniciar, vale lembrar de que é precisoincorporar uma nova crate, a tokio-timer = "*" , edisponibilizá-lanocódigo:

externcratefutures;externcratefutures_cpupool;externcratetokio_timer;

usestd::time::Duration;

usefutures::Future;usefutures_cpupool::CpuPool;usetokio_timer::Timer;

constPRIMO:u64=15485867;

fnmain(){

lettimeout=Timer::default().sleep(Duration::from_millis(500))

Adicionandotimeouts

192 16.3AVERSÃOASSÍNCRONA

Page 206: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

.then(|_|Err(()));

letprimo=CpuPool::new_num_cpus().spawn_fn(||{Ok(eh_primo(PRIMO))});

matchtimeout.select(primo).map(|(ok,_)|ok).wait(){Ok(true)=>println!("Primo"),Ok(false)=>println!("Composto"),Err(_)=>println!("Timeout"),}}

fneh_primo(numero:u64)->bool{foriin2..numero{ifnumero%i==0{returnfalse}}true}

Digamos que nosso código ficou mais pomposo agora queempilhamosfunções.Muitaspessoasdiriamqueéumcódigomaisfuncional, mas creio que ele só esteja mais simples e limpo. Aprimeira diferença agora é a criação do valor de timeout, comTimer::default(),comadefiniçãodeduraçãoeresultado.

Agora limpamos a variável primo de forma que elasimplesmente retorna a correspondência lógica da função eh_primo . Assim, aplicamos timeout em comparação aprimo,esperamospeloprimeiroechecamosoresultado.

Para as configurações definidas, teremos timeout; mas seaumentarmos o tempo de timeout ou utilizarmos um primomenor,como157,teremosovalorderetornoprimo.

16.3AVERSÃOASSÍNCRONA 193

Page 207: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

Duas funções diferentes e importantes apareceram agora, aselecteathen:

then:permitesequenciarfuturesparaqueelasrodemmesmo depois de receber o valor de outra. Neste caso,estamos alterando o valor da future timeout paraErr(()),mesmodepoisdeoutrathreadencerrar.select:combinaduasfuturesdomesmotipoparaqueelaspossamcompetirparaseencerrar.Elaentregaumpar,emque o primeiro componente é o valor produzido pelaprimeira future e o segundo joga de volta a outrafuture.Aqui,apenastomamosovalorOK.

Códigodoprojetoquerealizamosusandotokio-proto:https://github.com/naomijub/tokio/tree/master/tokio-proto/src

Código do projeto que realizamos utilizando futures:https://github.com/alexcrichton/futures-rs

Agora que vimos muitos exemplos de Rust no aspectofuncional e, principalmente, no concorrente, podemos concluiralgunsaspectosimportantes.Rustnãoéumalinguagemfuncional,mas foi claramente pensada de uma forma a permitir functionalfirst (programação funcional em primeiro lugar), o que nospermiteumdesenvolvimentolimpoeclarodeseucódigo.

Além disso, temos fortes aspectos de concorrência, que nospermitemchamara linguagemdeconcurrency first (concorrênciaprimeiro), já que o seu objetivo inicial era lidar com estes

Códigos

194 16.3AVERSÃOASSÍNCRONA

Page 208: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

problemassemrecorrerasoluçõescomplexasemC++,garantindoaperformance. Isso semostra real tambémquandocomparamosaspectos fortes deRust comClojure, uma linguagem funcional ecomaltacapacidadedeconcorrência.

AlgunsdesafiosinteressantesparacontinuarnossajornadaemRust podem ser o desenvolvimento de uma GUI que recebeupdates assíncronos, um algoritmo genético que as gerações nãoocorrem de forma linear e homogênea, ou até mesmo umacalculadora que divide seus blocos de cálculo para retornarmaisrápido.

Nomeuprojeto,exploramosapossibilidadedeutilizarRustdeforma a criarum servidorque conseguia se comunicarde formaassíncronacomobancodedados,egerarthreadsnãobloqueantesde sistema. Uma implementação assim pode ser extremamenteperformáticaemaisfácildemanterdoqueumservidorSpring.

16.3AVERSÃOASSÍNCRONA 195

Page 209: tcxsproject.com.br Livros... · Primeiro gostaria de agradecer à minha família e aos meus pais, por estarem sempre comigo e me apoiando. Obrigada, Diego e Kinjo, e obrigada aos

CAPÍTULO17

BLANDY,Jim.WhyRust?O'Reilly,2015.

KAIHLAVIRTA,Vesa.MasteringRust.Packt,2017.

NICHOLS, Carol. Increasing Rust’s Reach. Jun. 2017.Disponível em: https://blog.rust-lang.org/2017/06/27/Increasing-Rusts-Reach.html.

BIBLIOGRAFIA

196 17BIBLIOGRAFIA