IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langages de programmation Discussion :

« Home » : réflexions sur un nouveau langage


Sujet :

Langages de programmation

  1. #1
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut « Home » : réflexions sur un nouveau langage
    NdlM : discussion initialement ouverte depuis ce fil.

    Je suis ce fil de discution, car le sujet m'intéresse. Il m'intéresse parce que pour projet "perso", je suis en train de définir un nouveau langage et un compilateur pour ce langage.

    Je précise de suite que je n'ai pas d'ambition particulière pour ce langage, il ne vise pas a remplacer telle ou telle langage, et ne sortira certainement jamais de chez moi. C'est plus par plaisir. J'ai toujours voulu "écrire un compilateur", mais le faire pour un langage existant ne m'interesse pas du tout. D'où la création d'un langage.

    Je précise aussi, par honnêteté, que j'ai abandonner le C++ il y a plus de 15 ans, car sa "syntaxe" devenait de plus en plus imbuvale. C'est un avis personnel. Je n'ai pas abandonné le C++ pour les raisons qui sont discutées ici (la sécurisation de la mémoire).

    Je voulait que mon langage reste simple a lire et a comprendre. Je pourrai développer pourquoi, mais ce n'est pas le sujet.

    Je ne pratique pas Rust non plus, car je n'ai pas eu l'occasion d'y regarder de près, car dans mon domaine, c'est le C et rien d'autre.

    Ces petites précisions étant faites, je vais aborder le débat tel qu'il est posé ici. C'est à dire, faut-il passer du C++ à Rust.

    Je n'ai pas d'avis tranché sur la question, car si Rust apporte une sécurité de la gestion mémoire, j'ai du mal a comprendre pourquoi l'équipe de Rust a fait certains choix. Je pense que ces choix ont été fais car Rust ne voulais trop s'éloigner de la syntaxe "de base" du C++, afin d'attirer à lui ces derniers. C'est certainement un choix plus "marketing" que "technique". Mais je ne veux pas rentrer dans ce débât.

    Bref, je suis conscient que Rust apporte un plus au niveau sécurité, mais je pense qu'il aurait pu faire cela plus "simplement" (même si la solution Rust est tout à fait correcte, pertinante, utilisable et qu'on peut faire avec).

    Je vais tenter d'expliquer mon point de vue. Si je commet une erreur dans mon raisonnement, je suis tout à fait capable d'entendre les arguments contre "ma petit réfexion".

    Allez, je me lance. Le premier principe (pour la sécurisation de la mémoire) est l'Ownership.

    1./ Que le scope où est fait une allocation soit "propriétaire ET responsable" de cette allocation, je trouve cela tout à fait normal.
    2./ Que l'on désalloue la mémoire à la fin de ce scope, est tout aussi normal.

    Là où je pense que Rust a commis une erreur, c'est dans la 3ème règle, qui "transfert" la propriété au scope de la fonction appelée.

    Il "transfert" la propriété, mais si "on ne la lui rend pas" (ce qui est "possible", puisque non imposé), il ne vas pas permettre de compiler le code, car cela pourrait, si le scope la fonction appelée étant maintenant "propritétaire" déssalouait la mémoire à la sortie de son scope, provoquer un after-free-bug au niveau de l'appelant.

    Donc, pour réssoudre se problème, intervient le second principe de Rust, le "Borrowing". Borrowing signifie "emprunt", et "dans ma petite tête", emprunter ne veut pas dire donner. Cette notion de "Borrowing" utilise des "références partagés", mais non "mutable", ou une référence "mutable". Logiquement, on ne peut pas avoir les 2 en même temps, on peut utiliser soit une seule référence mutable, soit plusieurs référence "immutable".

    Mon "analyse" est-elle correcte jusqu'ici ? Il me semble que oui. Il est dit que cela n'est pas gênant, car tout les scénarios possibles peuvent fonctionner en utilisant ce "système". Je veux bien le croire, même si je n'ai pas vérifié que cette affirmation était vraiment valable.

    Mais pour vérifier tout cela, Rust nécessite un "Borrow Checker", qui va "surveiller" que ces règles sont bien respectées. Et refuser la compilation si ce n'est pas le cas.

    Suis-je toujours dans le vrai ? Il me semble que oui.

    Mais la cause, la racine de toute cette infrastucture, est la notion de "tranfert", et le fait que de fil en aiguille, il faut un "BorrowChecker" pour que l'ensemble fonctionne.

    L'équipe qui a développé Rust est très certainement d'un niveau supérieur au mien, je n'en doute pas. Je ne suis pas un théoricien et certainement pas un spécialiste des compilateurs (c'est le premier compilateur que j'écris, entièrement à la main) mais je suis un pragmatique il me semble que j'ai trouvé une "solution" plus simple et pratique pour le même résultat, que je suis en train d'implémenter dans mon petit langage perso. Je suis comme ça, j'aime simplifier.

    Pour que cette "solution plus simple" puisse être utilisée, il faut 1 condition.

    3./ Puisse qu'emprunté signifie "rendre à un moment donné" ce qui a été emprunté, il faut que le compilateur puisse obliger une fonction qu'on appel et qui a "emprunté" la "propriété" de la rende au "propriétaire d'origine". Ce qui n'est pas possible en Rust, me semble-t-il. Ce n'est pas possible, selon moi, mais dites-moi si je me trompe, parce que Rust ne fait pas la distinction entre ce que je nomme des paramètres d'entrées (qui sont contant par défaut), et des paramètres de sortie (variable par défaut, ce qui est somme toute logique) mais qui sont "obligés", par la syntaxe même d'une fonction d'être retournés à l'appelant, ce dernier ne pouvant pas ignorer ces dernier, ce qui est toujours le cas dans le type de fonction que j'ai implémenter dans mon langage.

    Voici comment on déclare une fonction dans mon langage :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    fct addition(int8 a, b) <- int16 result:
        result := a + b
    end
    a, et b sont des entiers 8 bits constants, intialisé lors de l'appel de la fonction. Ce sont des "paramètres d'entrées".

    result, placé après le symbole <- est lui un "paramètre de sortie" variable.

    Cette disctintion est un des points clef de ma méthode.

    Voici comment "on doit utiliser" cette fonction :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    int16 add
    add := addition(10, 30)
    3./ add est un entier 16 bits. Lors de l'appel de la fonction, il est donné en argument par le compilateur comme étant un troisiéme paramètre, utilisé sous le nom de "result" dans le corps de la fonction.

    4./ On ne peut pas, de la sorte, ignorer la valeur de retour, puisque cette valeur de retour est la variable qui est assignée. Le troisième paramètre est la valeur de retour.

    Si on applique le même principe lorsque l'on "prête" un "pointeur", cette méthode "assure par la syntaxe" que ce "pointeur" qui est emprunté est obligatoirement rendu à l'appelant.

    Conclusion:

    Il n'y pas besoin de "tranfert", il y a juste besoin "un prêt", dont on est assuré qu'il soit "rendu", et donc, pas besoin de "BorrowChecker" pour surveiller cela, puisqu'il est impossible, "par la syntaxe d'une fonction", d'ignorer que ce qui est rendu.

    On peut écrire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    fct allocation(int8 size) <- 'ptr' int8 result:
        result := alloc(size)
    end
    
    'ptr' int8 mem
    mem := allocation(250)
    Il n'y a même pas besoin de parler d'Onwnership, de Borrowing et de BorrowChecker pour utiliser correctement, et sans erreur possible une fonction.
    Le compilateur sait que result est un paramètre de sortie, et qu'il ne DOIT PAS le désallouer en fin de scope.

    Si l'allocation est 'interne' à la fonction, il le sait aussi, et DOIT le désallouer en fin de scope.

    Voilà voilà, j'attend vos critiques et/ou rélexions sur le sujet.

    BàV et "Peace & Love".

  2. #2
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    18 252
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 18 252
    Par défaut
    le langage ne pouvant pas garantir l'intégrité de toutes les manipulations il est à la charge du développeur de faire en sorte de les respecter.
    C'était la philosophie du C, en C le programmeur sait ce qu'il fait. Au départ C++ était une extension à C avant de diverger. Mais en réalité ce n'est pas toujours le cas car un débutant ne sait pas forcément ce qu'il fait car par définition il débute, et un expert peut faire des erreurs, l'erreur étant humaine,
    tout garde-fou est donc utile. Si Rust en est un pourquoi pas.
    Si Rust apporte des avantages, pourquoi ne pas l'utiliser ? Mais un expert C/C++ sera moins bon avec Rust.
    Rien n’empêche C++ d'évoluer en prenant en compte les avantages de Rust, la "concurrence" des 2 langages étant dans ce cas positif.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  3. #3
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Bonjour à tous
    @ jo_link_noir

    En fait si, l’emprunt est temporaire (si la fonction ne transfère pas ailleurs). Mais un emprunt se fait sur une référence, sinon c'est un transfert. Le transfert est l'équivalent d'une copie de la mémoire suivit d'un drop de la valeur précédente. Dans l'exemple que tu ne donnes de ton langage, il n'y a pas d'emprunt, seulement des transferts.
    Je dis justement qu'un emprunt ne peut être que temporaire, et qu'un moyen pour s'assurer qu'il est temporaire, c'est d'obliger l'empreuteur a rendre ce qui a été emprunté.

    Dire qu'un emprunt est "temporaire" sauf si la fonction le transfert, cela veut juste dire qu'il n'est plus "temporaire". Soit c'est "temporaire", (et donc un prêt), soit c'est pas "temporaire", et c'est alors un "transfert".

    Dans l'exemple que je donne, je prête un pointeur à la fonction qui fait l'allocation, est qui est obligée (par la syntaxe de la fonction), de retourner le-dit pointeur, puisque ce pointeur est la variable assignée. Je ne vois là aucun "transfert".
    Et que comme il n'y a pas de transfert, la fonction appellée (qui utilise le pointeur prêté pour faire l'allocation) ne devient jamais la propriétaire du pointeur. N'étant pas propriétaire, le compilateur ne désallouera pas le bloc mémoire qu'a alloué la fonction appelée. Et donc pas de bug *use-after-free*.

    Dans mon exemple, il n'y a pas de transfert, la propriétaire de la fonction appelante garde la propriété du pointeur.

    Au passage, faire une copie peut être "justifié" si c'est une petite copie, si il faut dupliquer un très gros bloc, ce n'est pas très optimal.

    Le code que tu donne en Rust, est, si on est de bonne foit, un rien moins lisible que mon approche:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    fct addition(int8 a, b) <- int16 result:
        result := a + b
    end
    
    int16 add
    add := addition(10, 30)
    est plus lisible que

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    fn foo(s: &String) { } // ne fait rien
     
    fn main() {
        let mut s = String::new();
        foo(&s);
        s.push_str("abc"); // on peut toujours modifier 's'
    }
    
    ou:
    
    fn main() {
        let mut a = String::new();
        // let b = a; // transfert
        a.push_str("abc"); // serait impossible si on décommente la ligne au-dessus
        {
            let b = &a;
            //a.push_str("abc"); // partage avec b, modification interdite
            println!("{}", b);
        }
        a.push_str("abc"); // ok, plus de partage, b est détruit
     
        let b = &a;
        //a.push_str("abc"); // partage avec b, modification interdite
        println!("{}", b);
        a.push_str("abc"); // ok, le partage s'arrête à la dernière utilisation de b
    }
    Hélas, je ne comprends pas comment tu as interprété les règles du borrow checker en Rust. Peux-tu donner des exemples sur les points que tu critiques ?
    Je ne critique pas le Borrow Checker de Rust, qui est pertinent en Rust, je dis simplement qu'on a en pas besoin si on aborde le problème d'un autre point de vue. Cet autre point de vue, c'est d'obliger (par la syntaxe) que la fonction à qui l'on prête ce pointeur, de le retourner. Le compilateur sait que le pointeur est retourné, et donc ne libérera pas la mémoire allouée via ce pointeur en fin de son *scope*, puisqu'il en a gardé la propriété.

    Si on est revient à Rust, il est obliger d'avoir ce BorrowChecker, car il doit surveiller l'emsemble.

    Encore une fois, je ne cherche pas a dénigrer Rust, je donne juste une autre approche qui me paraît plus simple. Et je la propose ici pour qu'on y trouve une erreur de raisonnement de ma part. Elle me semble plus simple, lisible est plus facile a comprendre.

    J'ai plus envie qu'on critique ce que je propose dans mon langage, que de m'expliquer qu'on sait faire la même chose en Rust (ou en C++ d'ailleur) d'une autre manière.

  4. #4
    Membre confirmé
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Mai 2021
    Messages
    101
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Mai 2021
    Messages : 101
    Par défaut
    Citation Envoyé par OuftiBoy Voir le message
    Le code que tu donne en Rust, est, si on est de bonne foit, un rien moins lisible que mon approche:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    fct addition(int8 a, b) <- int16 result:
        result := a + b
    end
    
    int16 add
    add := addition(10, 30)
    Tout n'est pas complètement clair.
    Est-ce que a et b sont des référence read only? ou des copies ?
    Je comprends que result est l'équivalent d'une référence mutable.
    Ai-je bien compris? Est-ce initialisé ("int16 add")?

    Que se passe-t-il si vous multithreadez votre appel à la fonction (en imaginant une telle fonctionnalité dans votre langage)?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    fct addto(int8 a) <- int16 result:
        result := result + a
    end
    
    int16 add = 0
    
    for a in {1, 2, 3, 4}
       new thread { add = addto(a) }
    En d'autres mots, comment le langage va gérer l'absence de data race sans imposer un fonctionnement monothread?


    Si ma compréhension de l'exemple est juste, le code Rust qui correspond est:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fn addition(a: &i16, b: &i16, result: &mut i16) {
        *result = *a + *b
    }
    
    fn main() {
        let mut add = 0;
        addition(&10, &30, &mut add);
        println!("add: {add}");
    }
    Et on récupère bien la pleine propriété sur la donnée add.

  5. #5
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut
    Citation Envoyé par fdecode Voir le message
    Tout n'est pas complètement clair.
    Est-ce que a et b sont des référence read only? ou des copies ?
    Je comprends que result est l'équivalent d'une référence mutable.
    Ai-je bien compris? Pourquoi ce n'est pas initialisé ("int16 add")?
    Je pense que je n'ai pas été assez clair, excuse-moi.

    Le premier exemple que j'ai donnée, n'est pas lié à la gestion dynamique de la mémoire.
    Il est juste là pour expliquer comment j'ai implémenté les fonctions dans mon langage.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    fct calcul('const' int8 a, b) <- 'var' int16 partie_entier, partie_reste:
        partie_entier := (a + b) / 3
        partie_reste := (a + b) % 3
    end
    Dans ce code:

    La fonction calcul possède:
    • 2 paramètres d'entrée (a et b de type int8),
      qui sont constant par défaut, mais j'ai mis 'const' devant.
      C'est à dire que la fonction calcul ne peut pas modifier ni a, ni b.
    • 2 paramètres de sortie (partie_entier et partie_reste de type int16).
      Ce sont des paramètres de sortie, qui sont retournés, et qui sont variable par défaut, mais j'ai mis 'var' devant.
    • La distinction entre les paramètres d'entrée et les paramètre de sortie c'est que ceux d'entrées sont entre parenthèses '(' et ')'
      et ceux de sortie sont après le '<-'.

      La fonction calcul a 4 paramètres.


    Lors de l'appel à cette fonction calcul ci dessous:
    x et y sont des int16

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    int16 x, y
    x, y := calcul(10, 30)
    Les 2 paramètres de sortie (partie_entier, et partie_reste) sont 'replacés' (par le compilateur)
    par les variable x et y qui sont de type int16.

    Le compilateur sait qu'il doit 'remplacer' 'partie_entier' par 'x' et à 'partie_reste' par 'y'.
    Tout comme il remplace a par 10 et b par 30.
    Sur la stack, le compilateur fait donc:
    push 10
    push 30
    push x
    push y

    et en fin de fonction, les valeurs sont récupérées via :
    pop y
    pop x

    Si on veut faire une allocation dynamique dans une fonction:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    fct je_vais_allouer(int16 size, int8 nb) <- 'ptr' int16 bloc_memoire:
        bloc_memoire := alloc(size * nb)
    end
    l'appel:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    'ptr' int16 mon_bloc
    mon_bloc := je_vais_allouer(30, 10)
    et safe & secure, car le compilateur sait que bloc_memoire est un paramètre de sortie (puisque placé après le '<-')
    paramètre qu'il DOIT retourné donc. Et il ne vas pas déallouer bloc_mémoire à la fin de je_vais_allouer.

    Par contre, si je fais:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    fct ma_fonction(size):
       'ptr' int16 mon_alloc_local
       mon_alloc_local := alloc(size)
    end
    Il va désallouer mon_alloc_local, car ce n'est pas un paramètre de sortie.

    Donc, les allocations sont *safe & secure*.

    Tu ne peux pas appel une fonction qui a 2 paramètres de sorties en faisant:

    x := calcul(10, 20)

    car le compilateur n'a que x pour 'partie_entier' et rien pour 'partie_reste', ça ne compilera pas et il y aura une erreur affichée.
    Pareil si x et/ou y n'ont pas le même type que 'partie_entier' et 'partie_reste'.

    Aussi, il n'y a pas de return dans une fonction.
    Les paramètres de sortie sont 'retournés' automatiquement en fin de fonction.
    C'est une mauvaise pratique que d'avoir plusieurs 'return' dans une fonction.

    Je pense que c'est plus simple à faire qu'a expliquer.
    C'est juste qu'on est "conditionné" a mélanger les paramètre d'entrée et de sortie entre les seules '(' ')'

    Par rapport à Rust, la fonction appelée NE PEUT PAS ne pas retourné UN PARAMETRE de sortie.
    C'est le problème de Rust, car comme il ne sait pas OBLIGER qu'on retourne à la fonction appelante la référence ou le pointeur (la troisième régle de l'Ownership et 'remplacée' par la notion de Borrowing), qui implique un 'BorrowChecker'.

    Avec ma méthode, pas besoin de tout cela.
    Mais je commet peut-être une erreur dans mon raisonnement. Mais je ne vois pas lequel actuellement.
    D'où mon post pour demander si quelqu'un voit un soucis avec ma méthode.

    BàT. et Peace & Love.

  6. #6
    Membre émérite

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    402
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 402
    Par défaut
    Citation Envoyé par OuftiBoy Voir le message
    Mais je commet peut-être une erreur dans mon raisonnement. Mais je ne vois pas lequel actuellement.
    D'où mon post pour demander si quelqu'un voit un soucis avec ma méthode.
    Le problème a mon avis avec tes "propositions", c'est que tu prends des exemples trop simples de types qui ne posent pas réellement de problème.

    Et plus généralement, c'est avec un raisonnement que tu peux montrer que ton design de langage est correct. Il faut montrer cela avec des études de cas plus larges, voir comment on va implémenter tel ou tel truc avec ton langage.

    Et c'est probablement une discussion qui est hors sujet ici.

  7. #7
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Je suis bien d'accord...
    Citation Envoyé par mintho carmo Voir le message
    Le problème a mon avis avec tes "propositions", c'est que tu prends des exemples trop simples de types qui ne posent pas réellement de problème.

    Et plus généralement, c'est avec un raisonnement que tu peux montrer que ton design de langage est correct. Il faut montrer cela avec des études de cas plus larges, voir comment on va implémenter tel ou tel truc avec ton langage.

    Et c'est probablement une discussion qui est hors sujet ici.
    Je comprend parfaitement ce que tu veux dire, mais c'est en développement, je pensais que j'avais donné le "raisonnement" de ce design, mais j'ai du mal a voir comment donner plus d'explications. J'ai montré un exemple avec des pointeurs (voir ma dernière réponse à fcode), mais je pense que je n'arrive pas bien a me faire comprendre. Si je suis "hors sujet", je m'en excuse, je pensais que j'étais même exactement dans le sujet.

    Les si les exemples sont trop simples, je reviendrais avec d'autres plus "compliqué". Mais l'idée de base est "simple", et j'ai "construit" ma (pas mes) "proposition" avec des exemple du même genre qui expliques que Rust est plus "secure" grâce à des principes, l'Ownership, et le Borrowing.

    1./ Que le "propriétaire" soit le scope où est faite l'allocation. Ok.
    2./ Que la désallocation soit faites automatiquement, en fin du scope du propriétaire, Ok. C'est ce que je fais.
    3./ Mais que si une fonction qui à qui l'on "transfert" la "propriété" de l'allocation, et que la fonction à qui l'on tranfert la "propriété" déssaloue (puisse elle qui en est le nouveau propriétaire), provoque un bug free-after-use au niveau de l'appelant, c'est gênant... d'où la notion de Borrowing de Rust.

    4./ Dans "ma proposition", c'est que la fonction qui a reçu la "propriété", soit "obligée" (par la syntaxe de la fonction), de rendre la propriété à l'appelant.
    5./ Dans la fonction appelée, la syntaxe permet au compilateur de savoir qu'il va devoir rendre la "propriété" à l'appelant, et que donc dans ce cas, il ne fait pas la désallocation automatique en fin du scope de la fonction appelée. Et donc on évite un bug free-after-use.
    6./ D'où l'idée de "forcer" la fonction a rendre la "propriété" à l'appelant. Qui lui fera la déssalocation à la fin de son *scope* (celui où a été faite l'allocation). Ce que ne permet pas de faire Rust. Donc l'apparition de la notion de Borrowing.
    7./ Il n'y a donc pas de notion de "transfert" dans ma "proposition", juste le "Borrowing", le pointeur (où quoi que ce soit d'autre) est "juste prèté" à la fonction qu'on appelle. C'est donc juste "un prêt", et comme c'est un "prêt", la fonction appelée "DOIT" rendre la propriété à l'appelant. Et la syntaxe que je propose permet de le faire.
    8./ Comme on est "certains" que la fonction appelante "récupérera" ce qu'elle a prêté, via la syntaxe que je propose, on évite automatiquement les bugs **use-after-free**, sans même devoir y penser, là au Rust doit mettre en place le BorrowChecker, car il ne peut pas faire autrement.

    En résumé, la fonction A demande à la fonction B de faire une allocation, en lui "prêtant" le pointeur avec lequel elle peut faire l'allocation. Quand B se termine, elle ne fait pas la désallocation, sachant que si elle le fait, cela créera un bug *free-after-use* au niveau de A, et est "obligée" (c'est même fait d'une manière "transparente") de retourner le pointeur que A lui a donné.

    Si la fonction A prête un pointeur qui pointe déjà vers un bloc, elle ne peut non plus déssalouer ce bloc, pour la même raison qu'elle est obligée de rendre le pointeur à A.

    C'est tout. Rien de plus.
    Je ne vois pas comment je peux expliquer cela plus simplement.
    Je demande juste si le raisonnement est correct et si j'oublie quelque chose.
    Pour le moment, je n'ai pas encore trouvé de cas où ma "proposition" peut créer un soucis.
    Et comme la discution était une bataille entre pro C++ et pro Rust niveau "sécurisation mémoire", j'ai expliqué cette proposition.
    Mais bon, je ne pensais pas que j'étais hors sujet, encore 1000 excuses.

    BàT et Peace and Love.

  8. #8
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Une dernière chose...
    Citation Envoyé par mintho carmo Voir le message
    Et plus généralement, c'est avec un raisonnement que tu peux montrer que ton design de langage est correct. Il faut montrer cela avec des études de cas plus larges, voir comment on va implémenter tel ou tel truc avec ton langage.
    Si tu veux, tu peux me donner un exemple, et je pourrai voir si "ma proposition" peut ou pas gérer le problème.
    Je n'ai pas dit que c'était infaillible, je cherche justement à savoir si ça l'est ou pas.

    Re-BàT. et toujours Peace & Love :-)

  9. #9
    Membre émérite

    Profil pro
    Inscrit en
    Décembre 2013
    Messages
    402
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 402
    Par défaut
    Citation Envoyé par OuftiBoy Voir le message
    Je ne vois pas comment je peux expliquer cela plus simplement.
    Tu n'as pas compris. Les exemples ne sont pas trop simples pour comprendre ton design, c'est pas trop compliqué. Ils sont trop simples pour voir les limites de ton design, quand ce langage sera utilisé pour des vrais applications concrètes.

  10. #10
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Bonsoir
    Bonsoir,

    Citation Envoyé par mintho carmo Voir le message
    Tu n'as pas compris. Les exemples ne sont pas trop simples pour comprendre ton design, c'est pas trop compliqué. Ils sont trop simples pour voir les limites de ton design, quand ce langage sera utilisé pour des vrais applications concrètes.
    Je comprend cela. C'est justement pour ça que j'ai exposé mon petit moint de vue. Pour voir s'il a qlq chose de flagrant ou un gros défaut dans l'idée de base. Il faut comprendre que je fais cela tout seul, et j'espèrais ici pouvoir communiquer pour essayer de voir "des erreurs de départ" dans mon idée de "design" (on appel ça comme on veut). J'essaye justement de savoir, si qlq'un disont plus "théoricien" que moi repère une faute flagrante.

    C'est le premier langage et compilateur que je fais, et je ne suis pas un "Théoricien", mais un "practicien". J'ai, je l'avoue une obsestion pour la simplicité, et je veux que mon langage reste simple pour l'utilisateur.

    Mais je comprend que je viens peut-être trop tôt, et avec des idée très différentes que toutes celles que j'ai vu passé devant moi depuis presque 20ans.

    Des "hype", j'en ai vu passé énormément, mais ce qu'il en reste au final, c'est très peut.

    Mais je te remercie d'avoir pris la peine de me lire.

    Merci, et BàT et Peace & Love.

  11. #11
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    OuftiBoy, si je comprends bien, dans ton langage, tu veux interdire le transfert de ressource de la fonction appelante à la fonction appelée.
    Quand une fonction appelée alloue une ressource, je n'ai pas compris si elle a le droit ou non de la transférer à la fonction appelante.

    En tout cas, pour vraiment concevoir les restrictions dans un langage, il faut avoir en tête ce que l'on y gagne et ce que l'on y perd.

    Par exemple, même si Rust est plus restrictif que C++, il n'est pas assez restrictif pour garantir l'appel des destructeurs. En effet, il est possible d'implémenter std::mem::forget en safe Rust. Ce problème est évoqué dans l'article Leakpocalypse. Une restriction possible pour empêcher ce problème est d'interdire Rc et Arc (qui sont analogues à std::shared_ptr en C++).

    Autre exemple, dans les propositions d'évolutions du Rust, Niko Matsakis a évoqué les must move types (plus communément appelés linear types) qui permettent de renforcer des contrôles à la compilation, mais cela rentre en conflit avec l'unwinding.

  12. #12
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Bonjour, merci de t'interesser à mes petites propositions.
    Bonjour,

    Citation Envoyé par Pyramidev Voir le message
    OuftiBoy, si je comprends bien, dans ton langage, tu veux interdire le transfert de ressource de la fonction appelante à la fonction appelée. Quand une fonction appelée alloue une ressource, je n'ai pas compris si elle a le droit ou non de la transférer à la fonction appelante.
    En fait, l'idée, c'est qu'il n'y ai pas de "transfert", mais uniquement un prêt en s'assurant que ce qu'on prête sera rendu.

    Exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    fct A(int8 size) <- ptr int8 mem:  
        mem := alloc(size)                 
    end                                           
    
    fct B():
        ptr int8 a                                              
        a := A(14)
    end
    Quand B veut appeler A, elle DOIT (par la syntaxe de la fonction A) le faire via une assignation à un ptr int8.
    Ici, a est le ptr assigné via a := A(14) dans la fonction B.

    Quand A est appelé par B, A alloue size int8 qu'il alloue via mem.

    Le compilateur ne va pas désallouer automatiquement mem à la fin du scope de A, puisqu'il sait que mem EST a et lui a juste été prêté par B. Et qu'il ne DOIT pas le désallouer, sinon B recevrait un ptr NULL.

    Mais quand B se termine, il va désallouer automatiquement a.

    Ce qui est donné en argument à A, c'est 14 pour size, et a pour mem

    Citation Envoyé par Pyramidev Voir le message
    En tout cas, pour vraiment concevoir les restrictions dans un langage, il faut avoir en tête ce que l'on y gagne et ce que l'on y perd.
    Jusqu'à présent, on ne perd rien par rapport à ce que fait Rust, et c'est plus 'simple' comme méthode me semble-t-il.

    Je n'ai pas encore trouvé de cas où ça pose soucis. Le <- ptr int8 mem implique qu'on doit appeler A via une assignation avec une variable de type ptr int8 (puisque c'est le type de mem).

    Appeler A:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A(14) ; erreur de compilation, il n' a pas d'assignation à un ptr int8.
    ptr int16 c
    c := A(65000) ; erreur de compilation, c est un ptr int16, et A attend un ptr int8 (mem)
    le seul moyen d'appeler A, c'est en faisant un :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    ptr a int8
    a := A(23)
    En résumé, en jargon 'Rust', le **scope** de B reste toujours propriétaire de a.
    Il le donne en argument à A via a:= A(14).
    A fait l'allocation, ne désalloue pas à la fin de son **scope**, car elle sait que a lui a juste été prêté en argument (car dans le code de A), ptr int8 mem reçoit a comme ptr. Et B sait qu'il va le récupèrer, c'est obligatoire via la syntaxe de A.

    Et pas de memory-leak non plus, a étant déssalouer automatiquement à la fin de son **scope**, celui de B.

    Il n'y a pas de notion de "transfert", juste un argument qu'on doit rendre.

    En Rust, on ferait un transfert de propriété et A serait le nouveau propriétaire de a.
    A étant le propriétaire de a il le désallouerait, sauf que comme il sait que ça ferait un bug 'use-after-free', il refuse la compilation.

    Pour que le compilateur accepte, il faut non pas tranférer a à A, mais seulement lui prêté (sans avoir la certitude que [A] va retourner a) , d'ou la notion de Borrowing. Et par conséquence le Besoin d'un Borrow Checker.

    Citation Envoyé par Pyramidev Voir le message
    Par exemple, même si Rust est plus restrictif que C++, il n'est pas assez restrictif pour garantir l'appel des destructeurs. En effet, il est possible d'implémenter std::mem::forget en safe Rust. Ce problème est évoqué dans l'article Leakpocalypse. Une restriction possible pour empêcher ce problème est d'interdire Rc et Arc (qui sont analogues à std::shared_ptr en C++).
    Je fais la distinction entre Rust (qui donne la garantie) et "unsafe Rust", réservé à ceux qui savent ce qu'il font, pour appeler des fonctions en C par exemple. Le mode "unsafe" est là pour ça.

    Citation Envoyé par Pyramidev Voir le message
    Autre exemple, dans les propositions d'évolutions du Rust, Niko Matsakis a évoqué les must move types (plus communément appelés linear types) qui permettent de renforcer des contrôles à la compilation, mais cela rentre en conflit avec l'unwinding.
    Euh, je veux bien te croire, mais en Rust, il y a déjà la notion d'Ownership + Borrowing quand l'Ownership pose soucis + Borrowe Checker pour surveiller le tout + Lifetime, ajouter des concepts sur d'autres concepts, ça fera vite une usine à Gaz. Je ne sais pas ce qu'est un "must move type", ni un "unwinding". Mais bon, je ne m'occupe pas de Rust, ils font comme ils veulent. Ils sont toutes une équipe, avec finacement, Théoricien, Ils bosent dessus depuis 10ans, etc... Moi je veux un langage simple, qu'un enfant de 14 ans peut comprendre. Mon langage (que je nomme Home au passage) ne doit pas utliser ce genre de machinerie. Et c'est mon premier langage, mon premier compilateur, (mais j'ai déjà écrit un RTOS) et je ne suis pas un théoricien, juste un Dev C à la base, et j'ai commencé il y a moins d'un an.

    Home doit rester simple a comprendre et à écrire, car le but final, c'est qu'il soit utilisable par de jeunes enfant, comme moi quand j'ai commencer à 13 ans avec le BASIC du C64.

    La je discute juste de sécurisée les allocations dynamiques, j'ai déjà tous les modules (lexer, parser, checker (gestion des types), IRC, Optimisation de l'IRC, Linker, AnalyzerStatique (s'assurer que dans une boucle on ne provoque pas d'underflow ou d'overflow, l'assembleur). On peut définir des type, faire de la POO mais via des interfaces, utiliser des simple structure, il y a encore les génériques à faire. J'ai encore du boulot pour peaufiner le tout, mais ça prend forme tout doucement... Le but final (si j'y arrive), c'est d'avoir ce que j'appelle un Homeputer pour faire des jeux 2D style 80's ou des application text-mode-like. C'est un ordinateur virtuelle (comme il y a des fantasy console style Pico8), qui serait bootable via une mini distri (genre Tiny Core), qui lance juste la VM, rapidement, et qui ne peut pas lancer autre chose. Je veux que ça 'boot' en 2 sec max, lancer directement la VM, et directement arriver sur un "éditeur". On pourrait ainsi récupérer des vieux portables, mettre juste une clef USB ou une carte SD, et donner le tout à un gamin de 14 ans, Bref, recréer l'expérience des micro-ordinateur des 80's.

    Le but n'est pas de concurrencer quoi que ce soit. Ni de remplacer quoi que ce soit. Je serais déjà content si dans 2 ou 3 ans je peux donner un vieux portable à mon petit fils, et qu'il découvre l'informatique (et la programmation) d'une manière simple, ce qui n'est plus le cas actuellement. Faut 2 min pour booter un PC, 3 min pour lancer un Visual Studio auquel un gamin de 13 ne vas rien comprendre. Là il pourrait en 2 sec écrire quelque ligne de code. Voir que son code fonctionne, et être émerveillé comme je l'ai été quant j'avait 13 ans, y'a 40 ans quoi...

    Merci de ton intérêt. Je sais que les exemples que je donne sont simples, mais comme je n'y trouve pas de faille, je demande juste qu'on critique mon idée pour l'améliorer.

    BàT & Peace & Love.

  13. #13
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    @OuftiBoy:

    Comment passer un pointeur à une fonction qui va le désallouer et ne pas le désallouer au niveau de l'appelant ? Puisque ce n'est pas un prêt.
    Comment modéliser des pointeurs partagés ? Typiquement, une fonction va chercher le résultat dans un cache ou qui alloue un pointeur.
    Comment stocker ces pointeurs partagés dans un tableau ?

    Citation Envoyé par OuftiBoy Voir le message
    Je fais la distinction entre Rust (qui donne la garantie) et "unsafe Rust", réservé à ceux qui savent ce qu'ils font, pour appeler des fonctions en C par exemple. Le mode "unsafe" est là pour ça.
    On dirait que tu n'as pas lu l'article, il est possible de l’implémenter en utilisant que des éléments "safe". (forget n'est de toute manière pas unsafe)

    Citation Envoyé par OuftiBoy Voir le message
    Je ne sais pas ce qu'est un "must move type", ni un "unwinding".
    J'ai l'impression que tu n'as pas lu non plus, c'est expliqué dans l'article: un type qui oblige plutôt qu'un type qui interdit.

  14. #14
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Bonsoir et merci de ton intérêt
    Merci de soulever ces questions.

    Effectivement, je n'ai pas lu toutes les références qui ont été citées. Pas parce que je les "snobe", mais j'essaye de trouver, sans être influencé par trop de "Techniques" existantes et/ou en développement. C'est aussi un sujet (les alloc/de-alloc), qui "pose souvent soucis", et j'essaye de partir d'une feuille blanche et sans appriori, quitte a redécouvrir la roue. Je n'ai pas non plus le temps de lire tout ce qu'il y à a lire.

    Mais tes questions sont très pertinantes, et je vais essayer d'expliquer comment je vois les choses.

    Citation Envoyé par jo_link_noir Voir le message
    @OuftiBoy:
    Comment passer un pointeur à une fonction qui va le désallouer et ne pas le désallouer au niveau de l'appelant ? Puisque ce n'est pas un prêt.
    Comment modéliser des pointeurs partagés ?
    Le ptr donné en argument de sortie "est un prêt", qui est "forcé" d'être rendu par la syntaxe, à la fonction qui a créer ce ptr. La fonction qui reçoit le ptr en argument de sortie, après le <- "DOIT" le retourner, c'est même fait automatiquement. Il n'y a pas de transfert.

    La fonction appelée ne PEUT PAS faire une désallocation automatique de ce ptr. C'est uniquement dans le scope où a été créé le ptr, que la déssalocation automatique SE fera.

    Citation Envoyé par jo_link_noir Voir le message
    Typiquement, une fonction va chercher le résultat dans un cache ou qui alloue un pointeur.
    Comment stocker ces pointeurs partagés dans un tableau ?
    La fonction qui reçoit le ptr fait ce qu'elle veut avec, mais ne PEUT PAS le déssalouer. Une fonction qui reçoit le ptr, peut "ajouter" des éléments à ce sur quoi ce ptr "pointe" avec des données dont on elle connait le type, ou en "retirer", MAIS doit quand même retourner le ptr en question.

    Si une fonction appelée "retire toutes les données" via le ptr qui lui est prêté, le ptr lui ne peut pas disparaître, être déssalouer.

    Tu peux créer dans une fonction un ptr qui ne pointe sur rien (dans ce cas, il pointe sur-lui même, il n'est pas à NULL), mais sait qu'il ne pointe pas sur des données, le donner à une fonction qui vas faire l'allocation des données dont elle connait le type, puis sera "obligée" de retourner ce ptr, et la fonction qui a créer le ptr, qui EST ET RESTE le propriétaire duptr, fait également ce qu'elle veut avec.

    La fonction appelée, qui va chercher des données "quelques part", ne PEUT le faire qu'en utilisant le pointeur ptr qui lui est prêté et qu'elle doit "retourner" (par la syntaxe) si elle veut pouvoir rendre ce qu'elle ajouter ou retiré via ce pointeur ptr qui lui est prêté. Ce qu'elle a été faire, elle est forcée de le faire via un un ptr reçu après le <-.

    Il n'a pas de pointeur "partagé".

    Il y a UN ptr, créer dans le scope d'une fonction, qui est prêté de fonction en fonction (qui ne peuvent pas le déssalouer) car situé après le <-.

    Citation Envoyé par jo_link_noir Voir le message
    On dirait que tu n'as pas lu l'article, il est possible de l’implémenter en utilisant que des éléments "safe". (forget n'est de toute manière pas unsafe) J'ai l'impression que tu n'as pas lu non plus, c'est expliqué dans l'article: un type qui oblige plutôt qu'un type qui interdit.
    Je les lirais, promis, mais je ne cherche pas a copier un technique, que je vais peut-être "redécouvrir".
    Il n'y a pas mode "unsafe" dans mon langage. Parce que je veux qu'un gamin qui découvre ce langage puisse le comprendre et l'utiliser facilement. Donc je tente d'éviter d'utiliser des "Techniques" trop compliquées.

    J'espère que j'ai réussi à répondre à tes questions, mais s'il y'a une faute dans mon "principe", si tu vois qu'il y a la possibilité d'une erreur, ça me ferais plaisir de le savoir, et je recherchais une solution pour réssoudre ce soucis. Je ne dis pas que mon "système" est "correct", j'essaye justement qu'on le critique pour y trouvers des erreurs et/ou des choses que se système interdirait de faire, et que j'aurais oublié.

    Merci, BàT, et Peace & Love.

  15. #15
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Citation Envoyé par OuftiBoy Voir le message
    La fonction qui reçoit le ptr fait ce qu'elle veut avec, mais ne PEUT PAS le déssalouer. Une fonction qui reçoit le ptr, peut "ajouter" des éléments à ce sur quoi ce ptr "pointe" avec des données dont on elle connait le type, ou en "retirer", MAIS doit quand même retourner le ptr en question.
    Comment ajouter des éléments dans un segment trop petit ? Typiquement, comment va fonctionner un push(vec, value) si push ne peut lui-même pas désallouer / réallouer puisque ce n'est pas le même scope ?

    Ton système me semble limité à ce qui se nomme les allocations temporaires, cela ne répond pas au besoin général. De plus, il est limité au pointeur, alors qu'un leak est plus large et englobe tous les handles: mémoire allouée, fichier ouvert, etc.

  16. #16
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Merci
    Citation Envoyé par jo_link_noir Voir le message
    Comment ajouter des éléments dans un segment trop petit ? Typiquement, comment va fonctionner un push(vec, value) si push ne peut lui-même pas désallouer / réallouer puisque ce n'est pas le même scope ?

    Ton système me semble limité à ce qui se nomme les allocations temporaires, cela ne répond pas au besoin général. De plus, il est limité au pointeur, alors qu'un leak est plus large et englobe tous les handles: mémoire allouée, fichier ouvert, etc.
    Je vois ce que tu ceux dire, voici (tu peux remplacer le type 'team' pour gérer d'autre 'type'):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    type team:
        ptr players int8 vec<len=?>
    end
    
    mtd team.add(int8 x):
        me.players.push(x)
    end
    
    team psg
    
    psg.add(10)
    psg.add(2)
    psg.add(15)
    Ici, team est un type (tu peux le voir comme un objet), mais un objet ce n'est qu'un type à qui, lorsqu'on l'instancie, le compilateur ajoute automatiquement un pointeur vers l'objet instancié.

    - En Python, ce serait l'équivalent de 'self' qui doit être explicite.
    - En C++, c'est le 'this'.
    - Ici, c'est 'me'.

    Le type team:

    • contient un ptr nommé players qui pointe sur un type vec (vec a une seule dimension et contient des int8).
    • Le compilateur sait que vec doit être alloué dynamiquement car,
    • les < > indique au compilateur que vec doit être est alloué dynamiquement.
    • len est le nom de "l'indexeur" du vec players, dont on ne connaît pas la 'taille' parce qu'elle peut varier, via le =? de l'indexeur len. On pourrait limiter à 11 dans le cas d'une équipe de foot et écrire <len=11>, qui limite la taille de vec à 11.
    • mtd team.add(int8 x) est une methode du type team, nommée add, qui a un argument 'classique' de type int8 et nommé x.
    • On pourrait passer par une class pour 'encapsuler' les datas du type team avec ses méthodes, mais l'encapsulation n'est qu'un concept. Ici, je dirais qu'on a "ajouter" la méthode add au type team.
    • team psg instancie un type team, qui se nomme psg.
    • psg.add ajoute un élément de type int8 au team psg.
    • via team.add, les éléments 10,2, et 15 sont ajoutés à son vec players via sa méthode push.


    la variable (tu peux dire objet) psg de type team sera désallouée lorsque sont scope se terminera.
    et elle fera disparâitre tout ce qui lui est lié, dont le vec players.

    Dans ce cas, il n'y a pas besoin de <-. Il n'y a pas de prêt, car il n'y a aucune raison d'en avoir.
    Comme je le disais au début, tu peux créer un autre type, pour gérer d'autres cas.

    Je te remercie vraiment de m'aider comme tu le fais, j'apprècie énormément. Si tu vois quelle chose qui ne va pas, qui manque, ou si je commet une erreur de logique qlq part dans ma réponse, ou si elle est incomplète, merci de m'aider a vérifier si mes "principes" sont valables où pas.

    Encore Merci 1000x.

    BàT et Peace & Love.

  17. #17
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 478
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 478
    Par défaut
    J'ai du mal à appréhender la finalité de "Home".
    Cela doit être un langage simple mais il y a des notions pointeurs qui est hors de porté des débutants.
    Basic, Pascal, Python, JAVA, etc ... ont fait en sorte de ne pas avoir à gérer des pointeurs.
    Pourquoi ne pas suivre l'exemple de ce type de langage, qui souvent repose sur un garbage-collector ?

  18. #18
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Merci de ton intérêt
    Citation Envoyé par bacelar Voir le message
    J'ai du mal à appréhender la finalité de "Home".
    Cela doit être un langage simple mais il y a des notions pointeurs qui est hors de porté des débutants.
    Basic, Pascal, Python, JAVA, etc ... ont fait en sorte de ne pas avoir à gérer des pointeurs.
    Pourquoi ne pas suivre l'exemple de ce type de langage, qui souvent repose sur un garbage-collector ?
    Je comprend ton point de vue. Je ne voulais pas (re)faire un compilateur pour un langage existant, d'où la création de Home.
    Il sera le language de ce que je nomme le "Homeputer", dont l'idée est de retrouver l'expérience des micro-ordinateurs des 80's.

    Une Machine Virtuel pourra compiler et exécuter le code .home. J'aimerai (je n'en suis pas encore là) que partant d'une Mini Distro Linux (je pense à TinyCore), seule la Machine Virtuelle puisse être exécutée, et rien d'autre. L'ordinateur booterais et lancerait cette VM le plus rapidement possible. Il fallait 2sec à peine pour lancer un C64 et arriver sur l'éditeur. J'aimerai, avec une distro légère et "minimal", qu'il en soit de même pour le Homeputer.
    J'aimerai qu'il n'y ait pas (comme c'est le cas actuellement malheureusement), trop de temps ni de courbe d'apprentissage trop grande entre l'enfant/utilisateur et l'accès à un langage de programmation.

    J'ai commencé la programmation à 13 ans avec le BASIC, mais une fois les limites atteinte, j'ai regarder vers l'assembleur. La VM permettra de commencer avec des choses simples, puis progressivement, introduire des "concepts" un peu plus compliqué, mais sans rentrer dans des choses trop compliquées non plus. C'est une équilibre que je tente de trouver.

    La VM s'affichera comme un "éditeur", du genre de l'éditeur du BASIC du C64. Avec un brin de "modernité bien choisie". Parmi la petite modernité, le Home pourra s'apprendre de façon incrémentale. Il y aura déjà moyen de faire des jeux 2D du style de ceux des 80's, sans devoir apprendre, ni même se rendre compte qu'on utilise des pointeurs. J'ai parlé des pointeurs, parce que le sujet de la discussion tournait entre la sécurité de Rust vs C++.

    Mais il y'a d'autres aspects, et j'essaye que ce "premier langage" qu'un enfant de 14 découvrirait, utilise "de bonnes pratiques". Donc pas avec les GOTO du Basic (par exemple).

    Pour la syntaxe, je m'inspire de Pascal et des premières versions de Python (dont la syntaxe a malheureusement tendances à se compliquer version après version). J'essaye de trouver un "juste milieu", entre "simplicité" et "capacité", avec un langage qui permettra de commencer par des choses très faciles, mais qui peuvent déjà donner un résultat "attirant" pour un enfant de 14 ans, et petit à petit, qu'il découvre d'autres aspect "un peu plus compliqués", sans devoir "changer de langage".

    Je veux effectivement qu'un gamin puisse écrire des Jeux en 2D, et le langage aura des constructions facilitant cela.

    Merci de ton intérêt.

    BàT. et Peace & Love.

  19. #19
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 478
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 478
    Par défaut
    Nous ne sommes plus dans les années 80 où on été cantonné aux Goupil, TO7, MO5, Vic20 et autres C64.
    L'éducation nationale a arrêté de perfuser ces "industriels".
    Elle a tourné la page avec Scratch et Python.
    Je pense qu'utiliser ces langages vus à l'école serait bien plus motivants que d'avoir à retenir un langage pas ou peu supporté.

  20. #20
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Mai 2015
    Messages : 337
    Par défaut Oui, Hélas..
    Citation Envoyé par bacelar Voir le message
    Nous ne sommes plus dans les années 80 où on été cantonné aux Goupil, TO7, MO5, Vic20 et autres C64.
    L'éducation nationale a arrêté de perfuser ces "industriels".
    Oh, mais je ne vise aucun "marché" particulier, que ce soit celui de l'éducation nationale, qui est soit dit en passant, en délabrement total... Ni un autre.
    Ce n'est pas un projet commercial, je n'ai pas cette ambition.

    Citation Envoyé par bacelar Voir le message
    Elle a tourné la page avec Scratch et Python.
    C'est son choix, elle fait ce qu'elle veut. Et moi aussi ;-)

    Citation Envoyé par bacelar Voir le message
    Je pense qu'utiliser ces langages vus à l'école serait bien plus motivants que d'avoir à retenir un langage pas ou peu supporté.
    Plus "motivant", je n'en suis pas certains... Avant de pouvoir créer un jeu 2D via Python ou Pygames, il y a une série d'obstacle a franchir. Le temps d'allumage de l'ordi. Le lancement de Python ou de Scratch. Le lancement d'un IDE, et d'autres notion. On est très loin d'un accès direct à un langage de programmation, et il faut déjà un peu, si pas beaucoup de temp, pour en apprendre les bases.

    Un des but, c'est qu'un enfants, allume le portable, même assez ancien, et qu'en qlq secondes, il soit directement pour lui possible de "programmer".

    Un parent pourrait donner un ordinateur à son gamin en étant certains qu'il ne pourra pas aller voir des sites porno, ou des influenceurs de tout bord.
    Il pourrait y ajouter des jeux déjà tout fait, etc...

    Dans la VM il disposerait d'outils "prêt à l'emploi" pour créer des graphismes, des sprites, des maps, etc...

    Un peut comme la Pico8, mais sur un portable récupéré et qu'un pourrait reconditionner en Homeputer "physique", ça éviterait de mettre au rebut des ordinateurs qui pourraient "reservir".

    Mais encore une fois, c'est aussi un projet qui me permet moi, d'apprendre comment définir un langage et d'implémenter son compilateur.

    Si ça intéresse d'autres personnes, tant mieux, sinon j'aurais compris un tas de choses en réalisant ce projet.

    BàT et merci, et Peace & Love.

Discussions similaires

  1. Réponses: 3
    Dernier message: 04/05/2024, 22h30
  2. Noulith : un nouveau langage de programmation construit sur Rust
    Par Bruno dans le forum Langages de programmation
    Réponses: 1
    Dernier message: 08/01/2023, 20h41
  3. Nouveau langage, conseils sur la grammaire
    Par Christophe Genolini dans le forum ALM
    Réponses: 0
    Dernier message: 21/08/2013, 10h09
  4. Nouveau langage : le D
    Par cheick dans le forum D
    Réponses: 4
    Dernier message: 30/05/2004, 15h56

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo