Clonando partições teimosamente (2)

Bem, como eu prometi, vou documentar o que eu tentei usar para clonar partições, muitas vezes indo contra os conselhos da internet, por teimosia, hehehe.

Aos poucos vou tentando encontrar os links de páginas que me ajudaram, e às vezes de coisas novas que resumem bem o assunto.

Os dois links abaixo explicam sobre o robocopy

http://serverfault.com/questions/409991/on-windows-is-it-safe-to-do-a-robocopy-in-order-to-clone-the-system

http://www.ijdens.com/home/usingrobocopytobackupallfilesfromadisk

Mas a página que eu usei mesmo como referência foi esta aqui:

http://winhlp.com/node/66

Analisei as opções do robocopy e vi o que seria necessário fazer além da cópia dos arquivos. Achei razoável e comecei os trabalhos.

Um exemplo de comando que usei foi:

robocopy d:\Windows c:\Windows /e /copy:datsou /r:3 /w:2 /zb /np /xj /log:robocopy_windows.log

(o exemplo acima é só para a pasta Windows, eu fui fazendo aos poucos para testar)

Embora haja ótimo suporte para copiar atributos, permissões e proprietários (/copy:datsou), o máximo de suporte a symbolic links e junctions é ignorá-los durante a cópia (/xj no exemplo acima, e também o /sl). Senão há o risco de fazer cópias infinitas recursivamente. Como uma instalação do Windows faz pouco uso desses recursos do sistema de arquivos, recriei as junctions e symbolic links de “Users” e “Program Files” manualmente usando uns poucos scripts.

Infelizmente esqueci dos hard links, e são milhares na pasta do Windows. Acabei ficando com uma cópia onde os arquivos hardemente linkados ficaram duplicados de verdade… Tentei corrigir com alguns scripts, procurando e recriando os links. Aprendi VbScript para complementar os scripts .bat, já que escrever muita lógica em .bat é muito sofrimento (e VbScript também está disponível no ambiente de recuperação iniciado pelo DVD de instalação do Windows, olha só). Acho que poderia ter sido JScript, que é quase igual a JavaScript, mas resolvi usar VbScript porque não precisaria duplicar todas as barras invertidas nas strings (VbScript aceita \ normalmente, JScript tem aqueles escapes precisaria trocar tudo por \\) e também para mexer em algo diferente. Consegui recriar alguns links mas ocorreram erros de permissão em alguns outros. Aprendi então uns comandos interessantes para alterar permissões de alguma pasta:

takeown /F alguma_pasta /R
icacls alguma_pasta /grant:r todos:f /T

Pois é, no Windows não basta você ser administrador e estar executando fora do sistema operacional cujos arquivos você quer copiar: ainda assim é necessário apropriar-se dos arquivos com takeown e alterar as permissões com icacls! No caso do robocopy, o tal “modo backup” da opção /zb deve ter dado uma ajudada para diminuir o sofrimento com permissões. Ah, e sim, o “todos” aparece no comando traduzido para a língua do sistema. A dica que eu segui dizia para usar everyone mas comigo o que funcionou foi o todos mesmo. Bizarro. O formato todo dos comandos é curioso, aliás: cada um tem seu jeito de indicar ação recursiva (/R e /T)…

No fim, mais ou menos neste instante, tendo alterado as permissões de toda a pasta Windows (a cópia), sem ter certeza quais hard links tinham sido corrigidos e quais faltavam no meu script, e sem saber como restauraria as permissões depois, decidi mudar a estratégia e fazer um clone da partição, apesar da cópia anterior ter resultado numa instalação do Windows funcional no novo disco rígido. Usei as ferramentas gráficas de edição de partições do Linux para criar um backup da partição original e restaurá-la por cima de outra. Mais especificamente, usei o gerenciador de partições do KDE, mas imagino que as capacidades não seriam muito diferentes se usasse o Gnome Disks que também tenho instalado (ainda estava seguindo a ideia de evitar instalar coisas novas…).

Não foi nenhum problema restaurar uma partição NTFS menor para uma maior em outro disco. A ferramenta suportou essa situação sem dificuldade. Exceto pelo setor de boot… O resultado era o tal do “blinking cursor”, o cursor piscante que não prossegue para o sistema operacional mas também não dá nenhuma mensagem de erro.

A descrição mais próxima do problema que me aconteceu eu encontrei aqui:

http://reboot.pro/topic/8233-problem-with-booting-cloned-xp-from-a-hdd-solved/

(o problema é identificado na mensagem #11)

Ou seja, o setor de boot dentro da partição inclui alguns valores de geometria de disco que estavam corretos logo após a formatação mas ficaram inválidos ao copiar outra partição por cima. No tópico é sugerido usar um tal de testdisk (no fim eu resolvi com o partclone.ntfsfixboot presente no Clonezilla).

Se tiver curiosidade sobre o Volume Boot Record de uma partição NTFS do Windows, encontrei este link: http://thestarman.narod.ru/asm/mbr/VistaVBR.htm

Se tiver curiosidade também sobre o MBR (Master Boot Record), aquele que fica fora das partições do disco, tem também esta página: http://thestarman.pcministry.com/asm/mbr/VistaMBR.htm

Já sobre particionamento GPT eu não aprendi nada nesta empreitada porque meu sistema não usa e eu já estava com atividades suficientes.

Sei que não seria impossível consertar byte-a-byte do VBR e gravá-lo com dd ou alguma ferramenta do tipo (já fiz algo parecido), mas não era exatamente o que eu tinha em mente. Eu queria limitar o número de programas usados, mas também não precisa ser tudo tão artesanal.

O problema ocorrido me surpreendeu porque eu imaginava que ou

  • o programa de copiar partições faria isso corretamente ou simplesmente indicaria que tal tipo de cópia não era suportado; ou
  • que qualquer ferramenta de correção de boot presente no próprio Windows consertaria isso

No fim, a verdade é que nenhum desses é capaz de corrigir isso. No Windows eu testei o bootsect.exe, bootrec.exe e o bcdboot.exe e a operação ou tinha sucesso mas não resolvia, ou não reconhecia o disco como bootável.

Mencionei na postagem anterior que era possível configurar o Grub para fazer boot da nova instalação a partir do boot loader do Windows, ignorando o boot record. Aprendi isso numa página como esta (não sei se foi exatamente esta):

https://ubuntuforums.org/showthread.php?t=2230300

e o conceito é relativamente simples: trocar o

chainloader +1

por

ntldr ($root)/bootmgr

Os outros comandos que vêm antes são basicamente carregamento de módulos para poder usar essa funcionalidade:

insmod part_msdos
insmod ntfs
insmod ntldr

e busca pela partição que será a variável $root

search .......
set root=........

Não copiei o script exato do Grub aqui porque os detalhes do funcionamento e a necessidade de tantos comando me deixaram confuso. No modo interativo do Grub bastou setar o root e chamar o chainloader ou ntldr, mas imagino que para rodar automaticamente o Grub tenha que se precaver e tentar mais de uma estratégia para encontrar a partição certa… Não facilita nada o fato de todos os sistemas operacionais e BIOS concordarem quem é o /dev/sda ou drive C: enquanto só o Grub atribuiu o outro disco para o hd0

Claro, mexer nessas configurações só funciona se você quiser continuar usando o Grub e não for apagar sua instalação do Linux. Para mim estava OK (consegui passar por toda essa aventura sem perder nenhuma partição! \o/), mas eu ainda queria encontrar uma solução que eu pudesse aplicar em outros computadores, caso viesse a ser necessário.

Foi aí que tentei o Clonezilla. Se termina com zilla deve ser bom, foi o que pensei, hahaha. Bem, o resultado da experiência com ele já foi descrita na postagem anterior, então nem precisa explicar muito mais do que já foi dito. A cópia não foi muito melhor que a tentativa anterior (exceto pela velocidade, que foi melhor), deu o mesmo cursor piscante (será que esqueci de marcar alguma opção?), mas esta FAQ resolveu, ao indicar o uso do partclone.ntfsfixboot:

http://drbl.org/faq/fine-print.php?path=./2_System/23_Missing_OS.faq#23_Missing_OS.faq

E esse é o fim da história. Fiz a cópia e não usei; mas não perdi nada e aprendi algumas coisas. Até editei as configurações do Grub para ocultar a partição copiada, que agora já está com o Windows desatualizado. De qualquer jeito estou escrevendo isto no Linux e faz um tempinho que não abro o Windows… Só não apaguei a cópia porque não estou precisando do espaço e serve como backup.

Clonando partições teimosamente (1)

Há alguns dias comprei um disco rígido novo para acrescentar ao meu computador. Inicialmente iria usá-lo apenas para dados (e programas, mas sem sistema operacional). Mas pensei: não é nenhum SSD com o qual eu vá ganhar desempenho movendo o sistema operacional para lá, mas pode ser um aprendizado interessante e pode simplificar a tabela de partições do meu disco existente que tem 5 partições e 2 sistemas operacionais (já teve 3 sistemas: WinXP, Win7, Ubuntu Linux 12.04 e agora tem Win10 e Ubuntu 14.04).

Fui ver na internet quais eram os métodos mais recomendados e os problemas possíveis ao mover um Windows 10 de um disco para o outro. Assim eu poderia tirá-lo da partição do meio do disco e ele ficaria apenas para dados e para o Linux, que na verdade é o sistema principal da máquina (portanto se eu estragasse a instalação do Windows o prejuízo seria mínimo). Tudo sugeria baixar algum programa para Windows (às vezes livre, às vezes em versão grátis com limitações) e deixar que ele fizesse tudo.

Aí que entra a parte “teimosamente” do título. Teimei que queria fazer o serviço com o mínimo de programas adicionais aos que eu já tivesse no Linux ou no Windows (instalado ou no DVD de instalação). Também teimei em não seguir as dicas de “não iniciar o sistema novo com o disco antigo ainda instalado”. O motivo de evitar programas novos era primeiro por imaginar que eles seriam muito opacos e inflexíveis (ex.: só copiar para novos discos vazios; ignorar ou atrapalhar-se com a existência de outros sistemas operacionais, ou até excluí-los; etc.), segundo por não querer arriscar encontrar neles uma fonte de adware ou spyware, e terceiro para aprender os detalhes do que estou fazendo.

O que aconteceu foi o seguinte:

  • Sim, é possível obter uma instalação funcional do Windows 10 em outro disco apenas copiando arquivos com o comando robocopy.
    • Desde que você use as opções certas e execute a cópia fora do Windows que está rodando (fiz no ambiente de recuperação presente no instalador).
    • Ainda assim, directory junctions, symbolic links, e hard links precisarão de muito cuidado especial para ficarem iguais. Fiquei com uma instalação funcional mas onde os hard links não eram mais hard links (os arquivos ficaram duplicados) e mesmo depois de muitos scripts escritos à mão desisti de consertar.
    • Um pouco de edição offline do registro da nova instalação pode ser útil para definir qual será o novo drive C:
    • É ineficiente, mas eu queria testar. Queria saber o quanto de “mágica” existe no sistema de arquivos do Windows. A conclusão é que ele é bem mais chato com permissões de arquivos e você pode ser surpreendido com alguns recursos obscuros (como as junctions e links, que normalmente não são usados no Windows), mas uma cópia arquivo-por-arquivo pode funcionar, sim.
    • Como não altera os setores de boot da partição, evita o problema de boot que menciono abaixo.
  • Fazer clone da partição com um editor de partições normal do Linux funciona, mas tem uma grande chance de deixar a partição não-inicializável.
    • Ao contrário do que eu imaginava, não havia nenhuma santa ferramenta do Windows que consertasse o boot da partição no novo disco. Perdi as contas da quantidade de vezes que tentei isso. Eu achava que elas reescreveriam todo o setor de boot, mas parece não ser o caso.
    • O Grub pode ser configurado para não carregar o Windows a partir do setor de boot, e sim a partir do bootmgr. Isso desvia do problema, mas não é exatamente uma correção.
  • Resolvi relaxar minha regra de não baixar programas novos e usar o Clonezilla. Software livre, baseado em Linux, com diversas opções. Deve resolver.
    • Uma omissão é que não clona para partições menores (que é útil quando se move para um SSD), ainda bem que eu não estava precisando disso.
    • Acabou resultando no mesmo problema de partição não incializável do caso anterior. Mas pode ser que eu tenha simplesmente esquecido de marcar alguma opção essencial para o tipo de cópia que eu queria fazer.
    • Já estava pensando em desistir totalmente ou usar um software específico para mover instalações do Windows para SSDs, que (tomara) seriam capazes de tratar todos os casos corretamente em vez de me deixar com uma cópia que não inicializa.
    • Depois de mais alguns dias descobri que no próprio sistema inicializado pelo drive USB do Clonezilla há um utilitário chamado partclone.ntfsfixboot (que na verdade é um software separado que eu estava pensando em baixar, mas tê-lo ali já é um incentivo a testá-lo), e ele finalmente foi capaz de consertar o setor de boot (obs.: estou falando do setor de boot que fica dentro da partição, não a MBR).

Entretanto, nesse momento eu já tinha meio que desistido e tinha até atualizado o Windows 10 na partição original para o Anniversary Edition (aquele que permite instalar o Ubuntu dentro do Windows, haha). De certa forma a cópia da partição que fiz antes serviu como backup. No fim não achei bom o desempenho de boot no novo disco. Talvez se rodasse um desfragmentador ou usasse por mais tempo daria chance do Windows otimizar o boot, mas nem sei se vale a pena. A esta altura já aprendi o que queria aprender: editei comandos do Grub durante o boot para testar diversas possibilidades e até VBScript eu usei para tentar consertar os hard links.

Gostaria de criar um novo post ou atualizar este aqui com mais detalhes do que tentei como programas, opções usadas, e links úteis. Quem sabe consigo fazer isso em breve.

Descobri o que me incomoda com Lisp-like languages

Não são só os parênteses. É a falta de pistas sobre o significado dos elementos (fora o primeiro de todos) das listas; e também porque tudo “cresce para dentro”, criando mais níveis de aninhamento. Quer uma variável? Põe mais um let ou um destructuring-bind e indente todo o resto do código mais um nível! (estou pensando principalmente em Common Lisp porque foi um dos Lisps que eu “tentei gostar”, mesmo sabendo que não seria o mais elegante de todos).

A falta de pistas é um caso interessante. À primeira vista, a linguagem parece incrivelmente regular: tudo é uma chamada com parâmetros, assim: (chamada param1 param2 param3). Funções, macros, formas especiais (*), tudo igual.

Mas daí você vê que num (if «condição» «código if true» «código if false»), nem tudo tem o mesmo valor. Não dá para começar a ler do meio, porque o terceiro parâmetro do if só executa se a condição for falsa, e não há separador entre as três partes (uma palavrinha “else” seria bem útil). Você vê (a 1) e isso pode ser a chamada da função “a”, ou pode ser a inicialização de uma nova variável, se estiver na posição correta dentro de um “let”, “do”, etc. Isso da “posição correta” é o que complica. Em linguagens como-C (C-like), é mais fácil começar a “ler do meio”. Tem uma palavra “else”? Você está no meio de um “if”, procure-o mais acima. Abriu/fechou {chaves}? Então você está dentro de um bloco, o que em Common Lisp seria um mais ou menos um “progn”. Encontrou um “=”? É uma atribuição a uma variável existente ou a uma variável nova. Você já sabe que o lado esquerdo não terá seu valor calculado, e sim alterado. Não há como surgir uma nova variável numa chamada(normal, com, parâmetros). Encontrou um ponto-e-vírgula? Descanse e comece de novo na próxima linha (descanse = faça um flush mental do seu parser de expressões).

Para um iniciante, no caso do let e suas inúmeras variantes (let*, letrec, aliás, para que tantas?), até dá para administrar, mas em construções usadas com menos freqüência como “do” ou “handler-case” dá um desânimo: quais parênteses são agrupadores da “sintaxe” e quais são parênteses normais?

Eu preferiria que houvesse algum indicativo mais claro para elementos como:

  • Nova variável. Clojure faz isso com colchetes.
  • Código “normal” de tamanho indeterminado. É o chamado “progn implícito” do Common Lisp, que pode surgir a qualquer momento.
  • Decisões. Num cond os blocos de código ficam muito misturados com as condições, bastaria algo como when/then para melhorar a visualização.
  • Símbolos. Em Lisp existe o quote (como em: ‘a), mas como as macros têm liberdade de fazer o que quiserem, e como colocar «’» em tudo ficaria feio de qualquer jeito, o uso do quote é inconsistente. Make-instance usa nomes ‘quotados, diversas outras construções (let, setf, handler-case, destructuring-bind) não usam.
  • Tipos, anotações, valores default e qualquer coisa opcional. Lisps têm a mania de usar mais um nível de parênteses sempre que for necessário adicionar algo que era opcional.
    • Variável num let? Você pode usar simplesmente o nome da sua variável. Ah, você queria inicializá-la, como normalmente é o caso? Então coloque-a entre parênteses junto com o valor.
    • Parâmetro de função? Basta o nome. Ah, você quer colocar um valor default? De novo: parênteses adicionais. Quer colocar um tipo num parâmetro de defmethod? Parênteses.
    • Slot dentro de um defclass? Lista de nomes entre parênteses. Atributos adicionais? Então agrupe cada slot com seus atributos em mais um nível de parênteses.

E não há necessariamente nenhuma lógica na escolha do que vai ser agrupado ou não. As variáveis de um let ficam entre parênteses no primeiro argumento, e o resto é código. Nada impediria que as variáveis e o código fossem separadas por um símbolo, como “in” (ML-like). Isso evitaria vários parênteses duplos. Ou o bloco de código (o progn implícito) é que poderia ter um delimitador (como parênteses ou chaves). Os slots de uma classe ficam entre parênteses, mas poderiam ser separados do resto com uma palavra, como “slots”.

A forma handler-case ilustra diversos pontos que incomodam: o primeiro parâmetro é o código protegido (o “try” de outras linguagens). Ele não é um progn implícito (mas poderia ser, por que não?), então se quiser fazer mais de uma coisa, você precisa colocar seu próprio progn. Depois vêm os tratadores de exceção (os “catch”), cada um entre parênteses. O primeiro item de cada é o nome da condition (sem nenhum indicativo especial, é tudo posicional). Depois, entre parênteses (precisavam de um delimitador, adivinha qual escolheram?), fica o nome da variável que receberá a condition. E então você tem um espaço livre para escrever seu código tratador de exceções usando os parênteses no seu significado normal.

É assim:

(handler-case
    (progn
      (do-stuff)
      (do-more-stuff))
  (some-exception (e)
      (recover se)))

Quando poderia ser:

(handler-case
    :try
      (do-stuff)
      (do-more-stuff)
    :catch some-exception :as e :do
      (recover se))

No exemplo hipotético acima, os parênteses são usados apenas para chamar funções/macros, enquanto que a estrutura dentro do handler-case é marcada de outras formas. Assim some-exception não fica parecendo uma chamada de função, a variável “e” é marcada com :as (que sugere ao leitor a ideia de um nome novo) e os blocos de código são marcados com :try e :catch/:do conforme seu propósito.

Dá para fazer isso com macros, claro que dá. Mas aí fica tão diferente da linguagem, que cada programador acaba tendo seu próprio dialeto. Se eu queria uma linguagem, acabei ganhando um kit de montagem em vez de uma linguagem usável.

O único lugar onde Common Lisp se permite usar algumas palavras a mais é no loop… Na verdade todas as ideias acima foram inspiradas no loop.

(*) Peraí, formas especiais? Pois é, nem Lisp escapa da necessidade de dar valor especial a certas formas. Fico agora pensando se TCL não acaba sendo mais regular…

Primeiros passos com Rust

Introdução

A linguagem Rust (https://www.rust-lang.org) é uma linguagem de programação que traz umas ideias novas bem interessantes para tapar o abismo que existe entre o C e C++ e (quase) todas as outras linguagens populares, combinando alto desempenho e abstrações leves com segurança para que uma operação errada não corrompa a memória.

Só isso já seria interessante depois de mais de uma década de interpretadores, runtimesvirtual machines, JITs e tal. Mas o mais interessante é a seu conceito de empréstimo de ponteiros (os tais de borrows).

Depois que declararam a linguagem estável na versão 1.0 em 2015, achei que valeria a pena experimentar um pouco com ela. Resolvi fazer uma tradução do tutorial de Scheme daqui http://www.inf.ufrgs.br/~vbuaraujo/blog/?entry=20160414-tour-de-scheme-2 para Rust. Um código simples é melhor para começar, porque o PdfPageCount que eu usava para comparar linguagens é meio assustador para iniciar numa linguagem totalmente nova.

O código está disponível em https://bitbucket.org/marcuscf/playground na pasta rust/agenda. Ele não é de forma alguma uma transcrição exata da versão Scheme: mudei o formato de arquivo para algo que eu pudesse manipular em poucas linhas de código, fundi funções que eu achei muito curtas (parece ser tradição em Scheme) e naturalmente a estrutura de dados usada não foi uma lista encadeada (outra tradição de Lisp, Scheme e demais linguagens funcionais, mas não é bem a coisa mais eficiente do mundo…). Aviso que não pensei muito na segurança na leitura de dados mal formatados. É só um teste da linguagem, não um programa super-robusto para controlar aviões!

Análise

Comecei da mesma forma que na versão Scheme, com funções de leitura do teclado e o menu principal. Até aí sem muitos problemas.

A primeira supresa veio na declaração da variável global “contatos”. Com let, erro. Vamos olhar o manual: para globais temos staticconst. Vai ser static então. Erro. Não pode ter static com destrutores (trait chamada Drop), não pode chamar funções complicadas na inicialização (pelo menos não na versão atual, segundo a mensagem de erro) e se declarar como mutável (mut), caímos na parte unsafe da linguagem. Até que está certo, uma variável global sendo alterada por múltiplas funções e múltiplas threads não é muito seguro. E Rust se esforça para garantir várias seguranças em relação a acesso concorrente em tempo de compilação!

Como variável global não é boa prática há umas boas décadas em qualquer linguagem de programação, não me importei em criar todas as funções recebendo um parâmetro do tipo &mut Vec e deixando a variável dentro da função main mesmo. Mas me importei, sim, em não saber como declarar algo que seja visível em todas as funções.

Por isso explorei a possibilidade de usar uma variável local da thread. Como o programa não seria multithreaded, seria o mesmo efeito de uma global. O modo como a variável deve ser obtida é interessante: com o método .with(), você passa uma função e somente dentro dessa função você poderá usar a variável. No fim, fiz isso num só local para testar como funcionava e no resto do programa continuei com a passagem de parâmetros como já estava fazendo antes.

A mutabilidade do Vec global eu consegui com a struct RefCell, porque a macro thread_local! não permite adicionar “mut” depois do static (e mesmo se permitisse, poderia haver outros problemas, como a exigência de blocos unsafe{}). No fim, a declaração ficou assim:

thread_local!(static CONTATOS: RefCell<Vec> = RefCell::new(vec![]));

(Caramba!)

e o uso (dentro de uma função) ficou assim:

CONTATOS.with(|contatos| { // ou |contatos: &RefCell|
    let contatos_mut = &mut *contatos.borrow_mut(); // o asterisco é opcional
    // (...)
});

(Caramba!)

Entendeu agora por que eu preferi continuar passando parâmetros em vez de fazer cada função obter a variável thread_local? Mas não deixei de usar a thread_local porque queria guardar um exemplo de forma de uso.

É, digamos que cada coisinha é bem explícita: a mutabilidade, os ponteiros, e cada vez que pegamos ponteiros emprestados (borrow). O asterisco ao fazer o borrow_mut() é opcional porque o resultado dessa função tem Deref automático. Uma das poucas coisas automáticas com ponteiros são a trait Deref e a chamada de método com o ponto. O resto todo é feito manualmente. À primeira vista me pareceu um design adequado: poucas coisas implícitas mas com alguns atalhos para os casos mais comuns.

As mensagens de erro do compilador são bem informativas. Apesar de não poderem exatamente ensinar a linguagem, elas informam muito bem o que o compilador estava esperando (um tipo, por exemplo) e onde o seu programa começou a diferir do esperado. Muitas vezes as mensagens são acompanhadas de dicas como “Quem sabe faltou importar o nome da trait” ou “Use tal sintaxe para solucionar esse tipo de situação”. Bem melhor que outros compiladores que só dizem algo como “Identificador desconhecido” ou “Tem algo errado mais ou menos aqui”.

A segunda surpresa foi o borrow checker, que é menos refinado do que eu pensava. Se você escreve uma chamada simples como

umaString.truncate(umaString.len() - 1)

ele já dá erro dizendo que o borrow mutável do truncate() não pode ser feito ao mesmo tempo que o borrow imutável do .len(). O mais lógico é que o len() fosse chamado e devolvesse o ponteiro para poder ser usado no truncate(), mas a implementação atual não leva isso em consideração. Para resolver, tem que chamar o len() antes, salvar o resultado numa variável e na instrução seguinte chamar o truncate().

No fim descobri que eu queria mesmo era chamar trim(), só não tinha encontrado o método na documentação até então. Até porque as linhas lidas pelo console no Windows estavam vindo com “\r\n” (e não só “\n”), então tirar o último caracter não era suficiente. Usei o trim() e era isso.

Se o exemplo acima pode ser solucionado com uma variável a mais, descobri diversas outras situações onde acontecia o contrário: a linguagem não permitia criar uma variável temporária, porque ela impedia de continuar usando a original até a temporária sair de escopo. Um exemplo disso está na função remover_contato(), onde eu queria fazer contatos[i].nome em passos separados (primeiro obter contatos[i], guardar numa variável, e depois obter o nome), mas para isso precisei criar um bloco na condição do if:

fn remover_contato(contatos: &mut Vec) {
    let nome_procurado = le_string("Nome a remover: ");
    for i in 0 .. contatos.len() { // não usei o for simples por causa da remoção
        if { // este bloco{} é necessário só por causa do escopo das variáveis
            let contato = &contatos[i];
            let nome_tmp = &contato.nome;
            nome_tmp == &nome_procurado
        } {
            contatos.swap_remove(i);
            return;
        }
    }
}

O bloco na condição do if serve para delimitar o escopo de contato e nome_tmp. Se eu não o utilizasse essas variáveis viveriam por todo o bloco do loop e eu não poderia fazer o swap_remove mais abaixo. Claro que neste caso não é nenhum problema escrever contato[i].nome numa expressão só, mas eu queria separar os pedaços para entender melhor a linguagem, e para casos onde essa separação de tarefas viesse a ser realmente necessária. Note também que a cada variável temporária usada aparecem uns &, pois temos que guardar os valores temporários por ponteiro, porque o default da linguagem é mover os valores, inutilizando a variável anterior. Esse default é útil para returns, e deve haver alguns outros casos também, já que li superficialmente na lista de discussão que moves explícitos tinham sido experimentados e deixavam a linguagem mais verbosa, com “move” pra tudo quanto é lado.

Uma curiosidade é que justamente enquanto testava esses casos, apareciam artigos na internet sobre esse mesmíssimo problema de borrows e como estão planejando solucioná-lo. Vejam estes links:

http://smallcultfollowing.com/babysteps/blog/2016/04/27/non-lexical-lifetimes-introduction/

http://smallcultfollowing.com/babysteps/blog/2016/05/04/non-lexical-lifetimes-based-on-liveness/

http://blog.rust-lang.org/2016/04/19/MIR.html

Conclusão

Neste pequeno programa pude conhecer apenas um pedaço da linguagem, mas já é muito mais conhecimento do que poderia obter apenas lendo sobre ela. Em geral achei a linguagem bastante amigável para sua categoria. Não é fácil como passar de Ruby para Python ou de C# para Java. Precisa voltar a pensar como em C++, com diversos tipos de referências, ponteiros, ponteiros para ponteiros e valores imediatos (quero dizer, aqueles tipos de objetos que ficam direto na pilha de chamadas ou são inseridos diretamente na struct em que estão contidos). Mas mesmo assim deve ser bem menos trabalhoso do que aprender as melhores práticas de como retornar strings de tamanho desconhecido em C sem estourar buffers nem deixar vazar memória.

Ridindaĵoj – Reformemuloj

Ĉiu esperantisto jam pensis/legis/debatis pri reformo, ĉu ne? Kaj ĉiuj aliaj timas pro atenco kontraŭ la stabileco de la fundamento. Tamen, kie estas esperantistoj, tie la temo reaperas.

Se ne eblas eviti proponojn de reformoj de Esperanto, oni povas amuziĝi per ili. Eble el ridindaĵoj aperos bona ideo. Aŭ eble la ridado fosos la tombon de iuj reformoj…

Kiel ĉiuj ni scias, Esperanto havas la finaĵon -n, por marki vortojn en la akuzativa kazo. Kaj tiu finaĵo estas ofte forgesata kaj misuzata de multaj homoj. Tamen, aliaj homoj (aŭ, nelogike, la samaj) tiel enamiĝis de akuzativo, ke ili trovis neakcepteble ke kelkaj frazoj ne havas la finaĵon -n ie ajn, malgraŭ la transitiveco de la verbo.

La ne-esto de la finaĵo -n povas okazi pro pluraj kialoj:

  1. La objekto ne estas Esperanta vorto.
  2. La objekto ne akceptas la finaĵon (iom, multe, pli, ambaŭ, unu, ktp.)
  3. La objekto estas subfrazo, titolo de verkaĵo, citaĵo, ktp.

Do, por solvi la situacion 1 (kaj foje 2), ili proponis novan prepozicion: na. La fakto ke la situacioj 3 ankaŭ ekzistas kaj kreos ne-necesajn kaj strangajn esprim-manierojn, kiel “na ke” kaj “na na” ne malhelpu reformemulojn.

Tamen, oni forgesis ke ekzistas alia grava finaĵo en Esperanto, la pluralo: -j. Do, kiel oni faru se oni volas diri ke oni legis du librojn de Harry Potter sen uzi la vorton “librojn”, kiu estas la nura ero kiu subtenas la finaĵon -jn?

Pro tiu gravega kaj neakceptebla manko de la lingvo, kompreneble oni devas uzi novan prepozicion: ja. La fakto ke tiu vorto jam ekzistas ne malhelpu reformemulojn.

La frazo do fariĝas: Mi legis ja na du Harry Potter. Tio estas multe pli logika! La fakto ke tiu ja estas nekomprenebla de ĉiuj jam ekzistantaj esperantistoj ne malhelpu reformemulojn.

Tamen, oni forgesis ke ekzistas alia grava frazrolo, krom la objekto: la subjekto, kaj ĝi ne estas markata.

Pro tiu gravega kaj neakceptebla manko de la lingvo, kompreneble oni devas uzi novan prepozicion: ka.

Estas tute evidente, ke “na” ne estas sufiĉa. Ofte okazas, ke la subjekto de frazo ne estas klare indikata:

1. Ĉu vi volas tiun libron, aŭ tiun ĉi?
Ambaŭ ni volas.

2. Ĉu vi volas libron, aŭ ŝi volas ĝin?
Ambaŭ ni volas.

Estas tute klare, ke “na” ne povas helpi tie. Do mi proponas novan prepozicion, “ka”, por indiki la _subjekton_ de frazo:

1. Ĉu vi volas tiun libron, aŭ tiun ĉi?
Na ambaŭ ka ni volas.

2. Ĉu vi volas libron, aŭ ŝi volas ĝin?
Ka ambaŭ ni volas.

Ka mi rekomendas, ke ka tiu nova prepozicio, ka kiu klare estos tre utila, estu ekde nun amplekse uzata. Ka tio certe evitos na multaj problemoj pri miskomunikado.

— de Vítor De Araújo

La fakto ke ka estas nekomprenebla de ĉiuj jam ekzistantaj esperantistoj ne malhelpu reformemulojn.

Tamen, oni forgesis ke ekzistas alia grava frazrolo: la predikativo.

Pro tiu gravega kaj neakceptebla manko de la lingvo, kompreneble oni devas uzi novan prepozicion: pa.

Ka mi rekomendas na, ke ka tiu nova prepozicio, ka kiu klare estos pa tre
utila, estu ekde nun pa amplekse uzata. Ka tio certe evitos ja na multa
problemo pri miskomunikado. Kaj ka Esperanto fariĝos pa multe pli klara lingvo.

Li farbos la blankajn domojn flavaj
fariĝos pa:
Ka li farbos ja na la blanka domo ja pa flava.

Ka tio ŝajnas al mi pa tre logika reform-propono. La fakto ke pa estas nekomprenebla de ĉiuj jam ekzistantaj esperantistoj ne malhelpu reformemulojn.

Se ka vi volas ja na pli da ja informo, bonvolu komenti tie ĉi. Ka mi imagas na, ke ka vi ĉiuj amos ja na ĉi tiu propono.

La fakto ke la rezulto kun ĉiuj proponoj kunaj aspektas kiel tute nova lingvo ne malhelpu reformemulojn, ĉu ne? Kiun problemon povus krei kelketaj novaj prepozicioj? /ironio

Rimarko: Ĉu oni devas diri ja ka, ja na, ja paka ja, na ja, pa ja? Ho, ve!

Simplificação ortográfica para aprender línguas

Ainda lembro quando um professor de inglês (que não era muito bom, em seguida descobri) disse que “não se escrevia” (não deveríamos escrever) a pronúncia das palavras ao lado delas. Duvidei, achei estranho, mas deixei para lá.

Hoje, muito tempo depois, posso afirmar com mais convicção que ele estava errado. Confiar na ortografia do inglês é pedir para aprender pronúncias erradas. Claro que as palavras mais comuns nós aprendemos corretamente, mas sempre fica aquela vontade de aproximar a pronúncia ao que é escrito (principalmente se o estudante aprende mais visualmente). Você pode ouvir “color” com a pronúncia correta, mas ao olhar aquele primeiro “o”, e ao ouvir colegas pronunciando a palavra de maneira aportuguesada, acabamos dizendo “cólor”, quando na verdade uma aproximação muito melhor seria “câlar”.

Esse tipo de coisa eu só fui descobrir depois ao ouvir inglês com pronúncia correta mais freqüentemente e também (aqui vem a surpresa): ao ler postagens em blogs sobre simplificação ortográfica do inglês. Sim, inglês escrito “errado” me fez aprender a pronúncia correta de muitas palavras. Eu leio a pronúncia fonética dos dicionários, mas não para cada palavrinha que encontro, havia muitas que eu pensava já saber ou que poderia deduzir e na verdade não sabia.

Claro que eu fico meio receoso em sugerir inglês escrito de maneiras não convencionais, porque pode causar confusão na cabeça do estudante (e até porque não deve nem existir material de aprendizado nesse formato), mas quem disse que a grafia oficial não causa confusão? Continuo gostando do alfabeto fonético, mas às vezes ele é preciso demais, e dá detalhes irrelevantes para os iniciantes.

Recentemente fiquei experimentando algo parecido com francês. Fico com medo de pegar manias erradas na hora de escrever, mas é muito mais fácil estudar sozinho se eu marcar explicitamente no papel quando “plus” deve ser pronunciado /ply/ e quando deve ser pronunciado /plys/. E também para saber que sans é /sɑ̃/ mas sens é /sɑ̃s/. Quer maneira melhor de fazer isso do que omitindo ou incluindo o S no final? Foi para isso que as letras foram inventadas, ora bolas (liason não mencionada para simplificar o texto).

Apêndice. Você sabia que…

Bomb, comb, tomb, além de terem o B mudo, não rimam?

As palavras debt (dét) e island (áiland) não tinham o B nem o S nem em línguas próximas, nem em formas antigas do inglês? Apesar disso, foram “restaurados” mesmo assim na escrita inglesa por “etimologia”. Etimologia de boteco, diga-se de passagem.

Build, busy, business, minute seriam muito mais fiéis à pronúncia correta se fossem escritos bild, bizzy, bizness, minat (ou minit)? Já ouvi muita gente tentando inserir um som de “iú” nessas palavras (já que esse é o nome da letra U em inglês), mas na verdade esse U está ali só para te enganar. O som é de I mesmo, aqui a lógica não funciona :-(

Money e funny rimam? Portanto money poderia ser escrito munny, mas antigamente alguém achou que ficava muito difícil de ler um monte de tracinhos verticais da seqüência m, u, n, então muitas palavras que deveriam ter “mu” pela lógica, são escritas com “mo”: month, mother, among, monkey. E também: nothing, tongue, come, some, done (estes últimos nem precisavam do E no final, ele só serve para atrapalhar). O fato de que bastaria escrever as letras um pouco mais separadas não deve ter passado pela cabeça dessas pessoas… Casos semelhantes são: color, other, touch, double, couple, mas aqui não sei o que explica o uso de o/ou.

Antigamente o V não existia? A letra U entre vogais era entendido como V. Por isso que, enquanto hat/hate, mad/made, hid/hide seguem uma lógica, have, live, give não seguem e têm um E no final desnecessário. Aliás, sempre que vejo live escrito fico em dúvida se é “liv” ou “live” (laiv), porque os dois existem e são escritos da mesma forma. Também existem várias palavras com ov que hoje poderiam ser escritas com uv: como love e above.

E tudo isso sem nem falar do -ough… Gostaria que o drive-trhu expandisse o uso do thru para todas as situações, porque o par through (lido como thru) e though (lido como tho) ninguém merece.

Crase – de segunda a sexta

Como um dos posts mais acessados do blog é Crase – A direção, de 2007, resolvi fazer uma continuação com o erro que eu mais vejo por aí.

Escrevem “de segunda a sexta” usando «à» com tanta freqüência que daria para até se confundir e achar que é com «à» mesmo… Se não fosse a lógica :D

Como eu já disse antes, a regra básica da crase é: «à» é o feminino de «ao». Você poderia, se quisesse, dizer:

da segunda ao sábado”

e portanto daí poderia dizer, por analogia:

da segunda à sexta”.

Mas na prática, não usamos da nem ao, usamos dea. Então deve ficar:

de segunda a sábado”

de segunda a sexta”.

Sem crase, portanto.

Note que as horas são um assunto completamente diferente.

das 08:00 ao meio-dia”

das 08:00 às 22:00”

das 08:00 à meia-noite”

Pode parecer complexo, mas é tudo lógico: se puder existir «ao», pode existir «à».