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

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    341
    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 : 341
    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 264
    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 264
    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
    341
    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 : 341
    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
    103
    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 : 103
    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
    341
    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 : 341
    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
    403
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2013
    Messages : 403
    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
    341
    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 : 341
    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 émérite

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

    Informations forums :
    Inscription : Décembre 2013
    Messages : 403
    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.

  9. #9
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    341
    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 : 341
    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.

  10. #10
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    341
    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 : 341
    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 :-)

  11. #11
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 503
    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 503
    Par défaut
    Clairement, vous n'avez aucune notion de ce qui se fait actuellement car toutes vos critiques tombent complètement à côté, no offense.

    Avant de pouvoir créer un jeu 2D via Python ou Pygames, il y a une série d'obstacle a franchir.
    On parle de sites Web qui demandent au maximum un login/password qu'on peut mettre en favori, aussi bien pour Scratch que pour des IDE online de Python. Aucune chance qu'une VM se lance plus vite qu'une page Web "sobre", et encore moins sur un petit coucou.
    Avec un langage totalement visuel comme Scratch qui s'adresse à de très jeunes enfants (bien moins que 14 ans), conçu avec l'aident de pédagogues spécialisés (mais majoritairement américains), votre Home est à des années-lumière de complexité de Scratch.
    Python a été choisi par l'éducation Nationale (qui continue à nous coller du Grec et du Latin à la place de l'algorithmie, merci la politique et la reproduction sociale) avec d'autres intentions que de faire plaisir à des cocorico-industrieux à la Minitel.
    Oui, l'éducation Nationale, c'était clairement pas mieux avant et les inspecteurs académiques et autres concepteurs de programmes éducatifs ne sont pas des politicards de Ministre en recherche du soutien des "vieux" (avec droit de vote).

    Ces 2 langages disposent chacun d'une ludothèque colossale toute en "Open Source".
    Avec des communautés de "jeunes" énorme et ils ont de la "hype".
    Et on parle même pas de truc comme Roblox, etc...

    Que Home ait un intérêt, peut-être, mais il doit être évaluer à l'aulne de ce qui ce fait actuellement, pour qu'il se positionne "correctement".

    https://scratch.mit.edu/projects/editor/
    https://www.online-python.com/
    etc...

  12. #12
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    341
    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 : 341
    Par défaut Oui et non...
    Bonjour

    Citation Envoyé par bacelar Voir le message
    Clairement, vous n'avez aucune notion de ce qui se fait actuellement car toutes vos critiques tombent complètement à côté, no offense.
    Je ne suis pas d'une génération qui se victimise en permanance pour un oui ou pour un nom. Donc pas de soucis de ce côté là. Ce que vous nommé "critique", je le nomme "constat". Je n'ai peut-être "aucune notion de ce qui se fait actuellement", soit. Quel est le problème avec cela ?

    Citation Envoyé par bacelar Voir le message
    On parle de sites Web qui demandent au maximum un login/password qu'on peut mettre en favori, aussi bien pour Scratch que pour des IDE online de Python.
    Je ne veux pas de "site web", je n'ai aucune attirance pour le web et ce qu'il est devenu. En 30 ans, on est passé de site "Web" où l'échange était possible, où des informations sérieuses et de qualités étaient la norme, à maintenant une vaste plateforme d'abrutissement, où l'on trouve 1 bon contenu pour 20 stupidités sans nom, le tout en étant traqué, et matraqué de publicité. Si on parle "technique", le développement web est une calamité, qui s'importe petit à petit sur le "Desktop". Bref, on est pas obligé d'être tous d'accord.

    Citation Envoyé par bacelar Voir le message
    Aucune chance qu'une VM se lance plus vite qu'une page Web "sobre", et encore moins sur un petit coucou.
    J'en doute fortement. Des pages "sobre", il y'en a de moins en moins. Une distro du genre TinyCore se charge en RAM, à partir d'un SD card ou d'une clef USB bien plus rapidement qu'un Windows ou qu'un Linux Mint. Considérer des PC de 5 ans sont bons pour la décharge, je trouve cela un gâchi immense. Mais encore une fois, si j'arrive jusque là, tant mieux, sinon, tant pis. Je n'ai pas d'ambitions particulière. C'est d'abord et avant tout un projet perso. Vous pouvez le trouver "stupide", "hord du temps", d'une "autre époque", c'est votre droit. Tout comme c'est le miens de voir les choses différemment.

    Citation Envoyé par bacelar Voir le message
    Avec un langage totalement visuel comme Scratch qui s'adresse à de très jeunes enfants (bien moins que 14 ans), conçu avec l'aident de pédagogues spécialisés (mais majoritairement américains), votre Home est à des années-lumière de complexité de Scratch.
    Je ne pense pas avoir dit du mal de Scratch. Si oui, merci de me montrer quand où j'ai exprimé cela. Scratch, a certainement un attrait, de par son côté visuelle, je n'ai pas dis le contraire. Mon gamin, via Minecraft et ses mécanismes, faisait celà il y a plus de 10 ans.

    Les "pédagogues spécialisés", veuillez m'excuser, mais ils ont boussillé toute une génération, avec des idées délirantes. L' idéologie de l'enfant-roi a été un désastre total. L'école est venu une garderie. Il suffit de comparer des archives, et le "niveau" scolaire est en chute libre depuis des lustres. Un enfant de 14 ans il y a 50 ans savait former une phrase correcte, avait un vocabulaire bien plus étendu, est une pensée bien mieux structurée qu'un jeune de 20 ans qui sait à peine parler correctement, avec 200 mots de vocabulaire max. Merci les "pédagogues spécialisés". Ma mère étaient institutrice maternelle, et constatait, même chez de très jeunes enfant de 6 ans un recul de leurs capacités d'année en année. Merci les "pédagogues spécialisés".

    Citation Envoyé par bacelar Voir le message
    Python a été choisi par l'éducation Nationale (qui continue à nous coller du Grec et du Latin à la place de l'algorithmie, merci la politique et la reproduction sociale) avec d'autres intentions que de faire plaisir à des cocorico-industrieux à la Minitel.
    Oui, Python est très certainement un bon choix, ai-je-dis le contraire ? Le Minitel était un délire "Franco-Français", tout comme "l'informatique pour tous" à l'époque de "Laurent Fabius", qui a été très longtemps à la tête de votre "Conseil d'état" qui se permet de "refuser" des lois votées par vos parlementaires.

    Si vous ne comprennez pas l'intérêt du Grec ou du Latin, c'est très dommage. Avant de faire de l'algorithmie, il faudrait peut-être apprendre à savoir compter jusque 10 et savoir exprimer une pensée claire. Nombres d'enseignant se plaignent que les enfants ne comprennent même pas les questions qu'on leurs poses. Il savent à peine lire ou écrire. Et il n'y a pas moyen de faire de l'algorithmie si on ne sait pas structurer le début d'une pensée.

    Citation Envoyé par bacelar Voir le message
    Oui, l'éducation Nationale, c'était clairement pas mieux avant et les inspecteurs académiques et autres concepteurs de programmes éducatifs ne sont pas des politicards de Ministre en recherche du soutien des "vieux" (avec droit de vote).
    On voit aujourd'hui le résultat des idéologies promuent par ces derniers. Plus de respect, plus de tenue, plus de connaissance dans les matières de base, le Français et les Mathématiques.

    Citation Envoyé par bacelar Voir le message
    Ces 2 langages disposent chacun d'une ludothèque colossale toute en "Open Source".
    Avec des communautés de "jeunes" énorme et ils ont de la "hype".
    Et on parle même pas de truc comme Roblox, etc...
    Ai-je dis le contraire ? Je n'ai pas l'ambition de me mettre en concurrence avec ceux-ci. Avec personne d'ailleurs. Je ne cherche pas cela. Ne me prêté pas des intentions que je n'ai pas.

    Ils ont de la "Hype" dites-vous. En voilà du beau Français... J'ai clairement dit que c'était un projet "perso" sans ambition particulière. Certains ont comme hobbit de regarder des séries, moi, actuellement, c'est de m'intéresser aux langages informatiques, et aux techniques de compilations. Je n'en ai rien à faire de la "Hype". Ai-je le droit de choisir mes hobbits ? Ou bien suis-je obligé qu'on m'impose ces derniers ?

    Que Home ait un intérêt, peut-être, mais il doit être évaluer à l'aulne de ce qui ce fait actuellement, pour qu'il se positionne "correctement".
    Encore une fois, même s'il n'a d'intérêt que pour moi, ça me plait de définir un langage et d'écrire son compilateur. Que je le positionne "correctement" ou pas n'est pas important. Et, sans offense, vous ne savez rien de mon petit langage, vous tirez des conclusions très "tranché" en ayant 1% de l'idée de ce qu'il sera et de son "environnement", car c'est en développement, rien n'est figé.

    Mais faite un test auprès des jeunes de maintenant, les victimes de vos "pédagogues spécialisés", et demander leur d'écrire un programme, dans n'importe quel langage que vous voulez, qui effectue un tri d'une série de 20 chiffres. Je prend cet exemple, car sur mon vieux C64, en 1982, j'ai "redécouvert" quelque choses qui "existait" déjà, le tri à Bulle, et j'ai compris pourquoi il était si lent dès que la série de chiffre a trier était plus grande. Ce n'est que 3 ou 4 ans plus tard que j'ai su que c'était une technique qui avait déjà été découverte (pas de web à l'époque, juste moi et mes 14 ans et 1/2 livres expliquant le BASIC) par d'autres. Savoir que j'avais pu, même si c'était la moins bonne manière de faire un tri, faire a 14 ans dans ma chambre, ce qui avait été trouvé par d'autres m'a rendu assez fier de moi, je vous l'avoue.

    Je suis un "auto-didacte". Pas un "Théoricien", et certainement pas un "Pédagogue spécialisé". Je vous invite à faire ce test. Prenez 10 enfants de 14 ans, et demander leur qu'ils implémentent avec "Scrath" (seul, sans personne pour les guider, sans manuel sur la question, et sans aller copier quelque chose trouvé sur le web), le tris de quelques chiffres, et puis, si certains y arrivent via un tris à bulle, d'expliquer pourquoi il est de plus en plus lent une fois que la série de chiffres augmente. J'ai su le faire à 14 ans en BASIC avec un C64 qui tournait à 1 MHz. Ils devraient en être capable, non ?

    Mais je vous remercie de vos remarques. On peut toujours tirer des informations ou des idées en échangeant, même et peut-être surtout avec des personnes qui ne pensent pas comme vous.

    Avec tout mon respect, BàV et Peace & Love.

  13. #13
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    341
    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 : 341
    Par défaut Bonjour
    Citation Envoyé par jo_link_noir Voir le message
    J'ai mal lu .
    Du coup non, y a pas.

    @OuftiBoy:

    ptr + vec, je ne comprends pas l'accumulation. Ni pourquoi un ptr seul à besoin d'être alloué, mais pas un ptr vec. Mais si je comprends bien, vec est builtin, on ne peut pas implémenter vec.add ?
    C'était juste pour montrer qu'on peut mettre ça dans un "Type".
    Sinon, oui, on peut faire ça directelent un vec.

    La syntaxe n'est pas figée, c'est en réflection;

    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
    ; tile est un type, prédéfinis ou pas.
    
    tile tab1[layer=4, y=50, x=80]   ; déclaration d'un tableau statique 3D.  layer, y, et x sont des "indexeur".
    tile tab2<layer=4, y=50, x=80> ; déclaration d'un tableau similaire en mémoire dynamique.
    
    int8 a=3, b=25, c=40
    tile tab3<layer=a, y=b, x=c>
    
    ; tab1 et tab2 sont des pointeurs, mais non exposés à l'utilisateur
    ; on ne peut accéder à des éléments des tableaux que via les indexeur, qui sont des "ranges", pas besoin de 3 boucles for "imbriquées"
    
    for layer, y, x in tab1:
        ; parcoureras tout les layer, y, x de tab1
    next
    
    for y, x in tab1[layer=0]
        ; parcoureras toutes les lignes et colonnes du layer 0
    next
    
    for y, x in tab2<layer=0>
        ; parcoureras toutes les lignes et colonnes du layer 0
    next
    
    tile une_tile := tab1[layer=0, y=0, x=0] ; assigne la tile qui est en 0,0,0 à une_tile
    Une fois dans l'éditeur:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    screen game
    game.print("Hello World", 0, 0)
    screen pourrait être soit un type prédéfini, où être dans une lib incluses automatiquement ou pas.

    Je rejoins bacelar sur la facilité d'utilisation: un pointeur est une notion compliquée pour un débutant, ce n'est pas simple. Plein de langages se débarrassent du problème en ce reposant sur un GC.
    Il y a des pointeurs, nécessaire pour faire des lib ou des type de base.
    Dans les exemples ci-dessus, ils y en a, mais le gamin/programmeur ne le sait même pas.

    On pourrait même avoir simplement:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    game.print("Hello World")
    si game est un screen automatiquement créer.
    il y aura différent mode, de très simple à plus compliqué.

    C'est l'idée.

    Qd je parle de VM, c'est juste un programme qui va interprèté le code binaire, c'est pas une VM comme une VM Linux qui tournerai dans un VM sous Windows.

    Chacuns a son avis, et c'est très bien ainsi.

    Moi je trouve qu'allumer l'ordi, booter sur un TinyCore (un mini linux fonctionnant en RAM), qui peut être sur une simple SD Card ou Clef USB, arrivant le plus rapidement possible à un éditeur intégré dans la VM. Et pouvoir suivre un Manuel qui indique comment afficher un "Hello Word" qlq seconde après le démmarage de l'ordi, être dans un environnement "cloisonné", et ne pas avoir d'obstacles, ni temporellement, ni intellectuement parlant, et avoir un résultat immédiat du "programme" écrit en 1 ligne, c'est une bonne idée. Si vous pensez que non, c'est votre choix que je respecte.

    Ici, le gamin a juste,

    - allumer son ordi
    - attendu qlq secondes
    - taper la ligne montrée en exemple dans le manuel
    - et il a le résultat immédiatement.

    Vous pouvez m'expliquer une manière plus rapide d'arriver à un tel résultat via Python/Pygame ou Scratch. En combien de temps, avec combien d'étapes ? Le gamin de 14 ans étant seul dans sa chambre ? J'aimerais savoir.

    Mais, encore une fois, si j'arrive jusque là tant mieux, sinon j'aurais pris du plaisir a développer tant le Home que la VM du Homeputer.

    BàV, et Peace & Love.

  14. #14
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    341
    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 : 341
    Par défaut Bonjour
    Citation Envoyé par mintho carmo Voir le message
    TL;DR : "c'etait mieux avant".
    Je n'ai pas dis cela, mais c'est devotre part un "argument" un peu facile, pour couper cours à toute discution et ne pas répondre à une simple question.

    Comme dans tout domaine, des choses étaient "mieux avant", et d'autres sont "mieux maintenant", ce n'est pas 0 ou 1.
    Ai-je fais du mal à quelqu'un ? Ai-je insulté quelqu'un ? Ai-je critiqué quelque chose ? On peut toujours réflèchir ou c'est interdir ça oui ? Suis nostalgique d'une époque, certainement. J'ai rarement connu quelqu'un qui n'était nostaligique de rien. Suis-je un danger d'essayer de "recréer" une exprécience que j'avais trouvée formidable lorsque j'était plus jeune ? Je ne suis pas venu poser des questions ici pour dénigrer qui que ou quoi que ce soit, j'ai justement demander qu'on critique UN des aspects de mon petit projet, et il me semble que j'ai répondu poliment et en argumentant mes réponses.

    Je ne suis pas ici pour perdre du temps avec des discutions stériles. Chacuns fait ce qu'il veut. Mais ce cite se nomme developpez.net, et est donc un endroit où l'on parle, et discute de programmation.

    Ceci dit, je vous souhaite une bonne fin de semaine, un bon week-end et plein de bonne chose pour la suite.

    BàV, et Peace & Love.

  15. #15
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Je reviens sur ce sujet après longtemps et je n'ai pas (encore) tout lu... Excusez moi pour les éventuelles redites
    Citation Envoyé par OuftiBoy Voir le message
    [...]

    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.
    Pourquoi devrait il en être ainsi

    Car, en définitive, il y a trois situations possibles:
    1. Soit le propriétaire "actuel" refuse de transférer la propriété, et la fonction qui s'attend à la recevoir ne peut être appelée.
    2. Soit le propriétaire "actuel" accepte de transférer cette propriété, et, du coups, c'est à la fonction l'ayant recue de prendre ses responsabilités, potentiellement sur base de la troisième possibilité
    3. soit le "nouveau propriétaire" n'est en quelque sorte qu'un "intermédiaire" entre deux propriétaire à "plus long terme"

    Il n"y a -- à mon sens -- aucune raison de privilégier une siutation par rapport aux deux autres, car les trois peuvent se présenter.
    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.
    A moins qu'il ne doive transmettre cette propriété à un autre élément qui en sera un propriétaire légitime "à plus long terme"...

    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.
    et que fais tu des deux autres possibilités envisagées
    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.
    Ben, ca, c'est déjà fait ... une référence constante sur un std::unique_ptr ne signifie pas forcément que la ressource ne peut pas être modifiée. Cela signifie juste que l'on ne peut pas demander au pointeur intelligent de libérer la ressource ou d'en prendre une autre en charge.

    Et, au pire, si l'idée de base est que le propriétaire "actuel" doit rester ... propriétaire de la ressource, rien n'empêche de ne transférer que la ressource sous forme d'une référence (constante ou non), pour être sur que la fonction appelée n'essaye pas de la libérer.

    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é.
    Et pourquoi, alors, ne pas tout simplement demander à la fonction A de s'occuper de l'allocation, puis s'arranger pour que la ressource transmise à B ne puisse en aucun cas être libérée en la transmettant sous forme de référence
    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.
    Déjà, pourquoi devrait elle prêter un pointeur Ne pourrait elle pas donner une référence à la place
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  16. #16
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    341
    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 : 341
    Par défaut Bonjour, et merci..
    Merci de soulever ces questions poliment et en avançant des arguments, c'est une démarche contructive que j'apprécie vraiment.
    Je vais tenter de répondre à vos questions, et n'hésitez pas à me contredire. Je profite de votre démarche pour répèter que je ne suis pas un "théoricien", que c'est le "premier langage" que je développe, et également le premier "compilateur". Je suis ne prétend nullement que mon approche est LA bonne approche, et que je suis ouvert à toute critique.

    Ceci étant dit, voici mes réponses:

    Citation Envoyé par koala01 Voir le message
    Je reviens sur ce sujet après longtemps et je n'ai pas (encore) tout lu... Excusez moi pour les éventuelles redites

    Pourquoi devrait il en être ainsi

    Car, en définitive, il y a trois situations possibles:
    1. Soit le propriétaire "actuel" refuse de transférer la propriété, et la fonction qui s'attend à la recevoir ne peut être appelée.
    2. Soit le propriétaire "actuel" accepte de transférer cette propriété, et, du coups, c'est à la fonction l'ayant recue de prendre ses responsabilités, potentiellement sur base de la troisième possibilité
    3. soit le "nouveau propriétaire" n'est en quelque sorte qu'un "intermédiaire" entre deux propriétaire à "plus long terme"

    Il n"y a -- à mon sens -- aucune raison de privilégier une siutation par rapport aux deux autres, car les trois peuvent se présenter.
    Il n'y a qu'un seul et unique "propriétaire", qui est le scope dans lequel a été faites une allocation dynamique, et qui a retourné un pointeur vers la mémoire allouée dynamiquement. Pour assurer qu'il est reste le propriétaire, il ne fait que "prêter" ce pointeur à une autre fonction. Le fait de ne pouvoir que "prêter" est assuré par la syntax du langage. Cette syntaxe fait la distinction entre ce que je nomme les "paramètres d'entrée", qui sont placés entre les () de la fonction appellée, et les "paramètres de sortie", qui se trouvent après l'opérateur '<-' dans la définition de la fonction. Ces paramètres de sortie SONT les variables qui sont assignées au retour de l'appel de la fonction.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // définition d'une fonction:
    fct do_something(int8 a, b) <- int16 sum, int16 mul:
        sum := a + b
        mul := a * b
    end
    
    int16 r1, r2
    r1, r2 := do_something(10, 6)
    Les variables r1 et r2 sont passée en argument de la fonction après l'opérateur '<-'. sum et mul sont utilisé dans la fonction, toute comme a et b. A la fin de la fonction do_something, sum et mul sont automatiquements retourné, et r1 se voit assigner sum, et r2 se voit assigner mul. En fait sum EST r1 et mul EST r2.

    Quelque soient les paramètres de sortie, et les appels que ferait la fonction do_something, le fonctionnement reste le même.
    Si l'on donne en argument de sortie un pointeur, le fonctionnement reste le même.
    L'appelant reste l'unique propriétaire "éternel", et la désallocation se fera lorsque son scope se terminera.

    Quand une fonction reçoit un pointeur comme paramètre de sortie, le compilateur "sait" qu'il ne doit pas en faire la désallocation, et ce par la syntaxe même du langage.

    le cas 1:

    Citation Envoyé par koala01 Voir le message
    Soit le propriétaire "actuel" refuse de transférer la propriété, et la fonction qui s'attend à la recevoir ne peut être appelée.
    Tout comme une fonction "normal" qui s'attendant à recevoir 2 arguments, créera une erreur si on l'appel avec 1 seul argument. Pour appeler une fonction, il faute respecter sa définition. Il n'y a rien de nouveau ici me semble-t-il ?.

    le cas 2:

    Citation Envoyé par koala01 Voir le message
    Soit le propriétaire "actuel" accepte de transférer cette propriété, et, du coups, c'est à la fonction l'ayant recue de prendre ses responsabilités, potentiellement sur base de la troisième possibilité.
    Le "propriétaire" ne transfert JAMAIS la propriété. Il ne fait que de la prêter, et la syntaxe du langage empêche qu'il en soit autrement.

    Citation Envoyé par koala01 Voir le message
    Soit le "nouveau propriétaire" n'est en quelque sorte qu'un "intermédiaire" entre deux propriétaire à "plus long terme"
    Il n'y a JAMAIS de nouveau propriétaires.

    Citation Envoyé par koala01 Voir le message
    A moins qu'il ne doive transmettre cette propriété à un autre élément qui en sera un propriétaire légitime "à plus long terme"...
    Il ne faut pas confondre la notion "d'utilisateur" et celle de "propriétaire". Un pointeur donné comme paramètre de sortie, peut être utilisé par d'autres fonctions, mais ne peut leur en donner la "propriété", car elle-même n'en a pas la propriété..

    Citation Envoyé par koala01 Voir le message
    et que fais tu des deux autres possibilités envisagées

    Ben, ca, c'est déjà fait ... une référence constante sur un std::unique_ptr ne signifie pas forcément que la ressource ne peut pas être modifiée. Cela signifie juste que l'on ne peut pas demander au pointeur intelligent de libérer la ressource ou d'en prendre une autre en charge.
    C'est un peut la même idée, en effet. Par exemple, une fonction peut allouer un tableau de 10 entiers, via la syntax:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        tableau_alloue_dynamiquement := <int8 x=10, y=10>
        tableau_allouer_statiquement := [int8 x=10, y=10]
    x et y sont des "indexeurs", et l'on peut uniquement passer par eux pour accéder au tableau. Par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
       for x, y in tableau_allouer_dynamiquement:
          ...
       end
    
       for x, y in tableau_allouer_statiquement:
          ...
       end
    Citation Envoyé par koala01 Voir le message
    Et, au pire, si l'idée de base est que le propriétaire "actuel" doit rester ... propriétaire de la ressource, rien n'empêche de ne transférer que la ressource sous forme d'une référence (constante ou non), pour être sur que la fonction appelée n'essaye pas de la libérer.
    C'est ce qui est fait, mais avec un syntaxe nettement plus lègère.

    Citation Envoyé par koala01 Voir le message
    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.

    Et pourquoi, alors, ne pas tout simplement demander à la fonction A de s'occuper de l'allocation, puis s'arranger pour que la ressource transmise à B ne puisse en aucun cas être libérée en la transmettant sous forme de référence

    Déjà, pourquoi devrait elle prêter un pointeur Ne pourrait elle pas donner une référence à la place
    C'est exactement ce qui est fait, mais il n'y a que des pointeurs, qui peuvent être "prêtés" à d'autres fonctions, qui ne pourrons pas effectuer de désallocation, car elle ne SERONT jamais propriétaire. "Théoriquement", on pourrait dire qu'un pointeur donner en paramètre de sortie est une "référence".

    Voilà, j'espère avoir répondu à vos questions.

    Tout ceci est en développement, mais commence à se stabiliser petit à petit, et les notions a s'affirmer.

    Je n'ai pas d'ambition pour que ce langage deviennent un langage "mainstream". Mais ce n'est pas uniquement non plus un "travail académique", ce langage n'est qu'une "partie" d'un projet bien plus "large". Mais il est encore trop tôt pour évoquer ce projet, qui avance cependant lui aussi petit à petit...

    Je vous remercie encore, et si vous pointez une "erreur" dans mon raisonnement, je suis tout à fais ouvert à la discution.
    Lorsque tant le langage que son compilateur seront arrivés à une première étape "stable", je compte rendre public tant le langage que le compilateur, le tout expliqué dans le détail.

    BàV et Peace & Love.

  17. #17
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Il n'y a qu'un seul et unique "propriétaire", qui est le scope dans lequel a été faites une allocation dynamique, et qui a retourné un pointeur vers la mémoire allouée dynamiquement.
    Justement, là, vous vous contredites...
    Prenons un exemple (code "C++ like" )
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    PtrType * allocate(/* params*/){
        /* some way to dynamically allocate memory */
       return popinter
    }
    void foo(){
        PtrType * ptr = allocate(/* param*/);
    }
    Qui est le propriétaire "légitime" du pointeur dans ce bout de code :question :

    Est-ce la fonciton allocate (qui a effectivement alloué la mémoire) ou est-ce la fonciton foo, qui récupère le pointeur sur la mémoire allouée

    Et, peu importe comment vous allez organiser les choses, même si c'est en introduisant un effet de bord volontaire (ce qui n'est que rarement une bonne solution) sous une forme qui pourrait être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* here, allocate return true if allocation succeeds, false oftherwise */
    bool allocate(PtrType *& refToPointer, /* other params*/){
        /* some way to dynamically allocate memory  for refToPointer*/
       return success_or_failure;
    }
    
    void foo(){
        PtrType * ptr;
        allocate(ptr, /* other params */);
    }
    Au bout du compte, le propriétaire légitime d'une ressource allouée dynamiquement à partir d'une fonction appelée sera toujours ... la fonction appelante.

    Alors, oui, vous avez (en partie seulement !!!!) raison : il ne peut y avoir qu'un seul propriétaire.

    Cependant, vous oubliez une précision des plus importantes : il ne peut y avoir qu'un seul propriétaire A UN INSTANT DONNE DE L'EXECUTION. Et ce sont ces six mots qui font toute la différence.

    Car, sans ajouter cette précision, ce que vous dites, c'est que c'est la foncton allocate qui devrait être le propriétaire légitime de la ressource allouée dynamiquement, et donc, qu'elle devrait prendre en charge la libération de cette ressource lorsque l'exécution en quitte le scope. C'est à dire ... juste avant de rendre la main à la fonction foo (dans mon exemple). Ce qui rendrait l'allocation dynamique de la ressource ... totalement inutile, vu qu'elle ne pourrait pas être utilisée hors de la fonction qui l'a effectuée (ou en dehors des fonctions que la fonction allocate pourrait appeler).

    Par contre, si vous précisez qu'il ne peut y avoir qu'un seul propriétaire légitime de la ressource A UN INSTANT DONNE DE L'EXECUTION, alors, là, les choses deviennent différentes.

    Parce que cela signifie que la fonction allocate peut "refiler la patate chaude" à la fonction foo (dans mon exemple toujours) pour que ce soit la sortie du scope de cette fonction qui occasionne la libération correcte de la ressource; et, pourquoi pas, que la fonction foo déciide de refiler cette responsabilité soit à une des fonctions qu'elle appelle par la suite, soit à la fonction qui l'a appelée.

    Le "propriétaire" ne transfert JAMAIS la propriété. Il ne fait que de la prêter, et la syntaxe du langage empêche qu'il en soit autrement.
    Justement, comme je viens de vous l'expliquer, c'est sur ce point que vous vous trompez.

    Sans possibilité de transférer la propriété, vous allez vous retrouver à devoir allouer l'ensemble des ressources ... dans votre fonction principale. Car ce sera le seul endroit où vous pourrez être sur que l'ensemble des ressources restera accessible aussi longtemps que vous pourriez en avoir besoin; ce qui risque d'amener un autre problème du "et si j'ai besoin de plus d'espace".

    Il n'y a JAMAIS de nouveau propriétaires.
    Non, vous pouvez imposer le fait qu'il n'y a jamais plus d'un propriétaire A LA FOIS pour une ressource. Ce sera d'ailleurs sans doute une excellente chose.

    Par contre, si vous empêchez le transfert de responsabilité d'un propriétaire vers un autre (étant entendu que "l'ancien" propriétaire n'a alors plus rien à dire quant à la libération de la ressource), vous allez vous limiter dans vos possibilités de manière horrible...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Il ne faut pas confondre la notion "d'utilisateur" et celle de "propriétaire". Un pointeur donné comme paramètre de sortie, peut être utilisé par d'autres fonctions, mais ne peut leur en donner la "propriété", car elle-même n'en a pas la propriété..
    Justement, je suis le premier à insister sur la nécessité de clairement séparer la propriétaire de l'utilisateur.

    Ce dont vous ne semblez pas avoir conscience en terme de ressource allouée dynamiquement, c'est que soit vous devez transférer aux fonctions appelées la ressource elle même sous une forme qui empêchera la fonction appelée de libérer la ressource en question (en C++, nous utiliserons de préférence une référence pour cela), soit c'est carrément le propriétaire en entier que vous devrez transférer, pour que, si vous décidez -- à un moment donné, dans la fonction appelée -- que la ressource peut effectivement être libérée, ce soit effectivement fait en demandant au propriétaire de s'en charger.
    Par exemple, en C++, cela donnerait quelque chose comme
    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
    28
    29
    30
    31
    32
    33
    34
    35
    /* Cette fonction utilise une ressource, mais ne peut décider de la libérer */
    void user_no_release( Type /*const */ &  ref){
        /* on peut utiliser la ressource, la modifier si la référence
         * n'est pas constante, mais pas décider de libérer la ressource
         */
    }
    /* cette fonction peut décider de libérer la ressource */
    void possibly_release(std::unique_ptr<Type> & ptr){
        /* some stuff here*/
        if( some_condition)
            ptr.release(); /* parce qu'on a le propriétaire en entier,
                            * on peut lui demander de libérer la ressource
                            */
    }
    void foo(){
         /* le propriétaire de la ressource n'est pas la fonction foo
          * mais bien la donnée unique_pointer
          */
         std::unique_ptr<Type> unique_pointer= std::make_unique<Type>(/* param*/);
         /* on peut appeler une fonction utilisatrice en transférant "ce qui est pointé
          * par le pointeur sous jacent
          */
        user_no_release(*unique_pointer);
        /* on peut aussi transmettre le propriétaire en entier */
       possibly_release(unique_pointer);
       /* seulement, arrivé ici, unique_pointer pointe peut être
        * (si "some_condition" a été vérifiée dans la fonciton appelée)
        * vers une adresse connue pour être invalide (nullptr)
        * Si bien que, si on veut encore accéder au pointeur sous-jacent,
        * il faut commencer par s'assurer de la validité du pointeur
        */
        if(unique_pointer) // si le pointeur est valide
            user_no_release(*unique_pointer) // on peut continuer à l'utiliser
    } // dans tous les cas, comme on quite le scope dans lequel unique_pointer a été déclaré
      // si le pointeur sous-jacent est encore valide, la ressource pointée sera libérée ici
    Par simplicité code tend à valider le fait qu'il n'y a qu'un seul et unique propriétaire, ce qui est tout à fait vrai au demeurant.

    Mais, qu'est ce qui pourrait empêcher ce propriétaire de "se balader" d'une fonction à une autre, de manière à ce que la sortie de scope d'une autre fonction que celle dans laquelle il aura été déclaré provoque effectivement et automatiquement la libération de la ressources

    Après tout, si la fonction foo prenait une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void foo(std::vector<std::unique_ptr<Type> & tab){
        tab.emplace_back(std::make_unique<Type>(/*params*/));
        size_t index= tab.size()-1;
        /* on peut appeler nos deux fonctions */
        user_no_release(*(tab[index]));
        possibly_release(tab[index]);
    }
    la sortie se scope provoquant la libération de la ressource se ferait ... au niveau de la fonction dans laquelle le tableau a été déclaré.

    Mais qui est le propriétaire légitime de la ressource
    Je n'ai pas d'ambition pour que ce langage deviennent un langage "mainstream". Mais ce n'est pas uniquement non plus un "travail académique", ce langage n'est qu'une "partie" d'un projet bien plus "large". Mais il est encore trop tôt pour évoquer ce projet, qui avance cependant lui aussi petit à petit...
    Je comprend bien. Et je dirais que c'est une raison de plus pour prendre le temps de réfléchir correctement à l'outil que vous essayez de créer.

    Car, finalement, un langage de programmation n'est jamais qu'un langage comme le Français : un ensemble de conventions permettant à deux intervenants de se comprendre. La seule différence avec les langages "natifs" étant que l'un des intervenant est ... aussi bête qu'un ordinateur. C'est d'ailleurs la raison pour laquelle l'ordinateur devra utiliser un outil (interpréteur ou compilateur) pour arriver à comprendre ce que les humains auront écrit.

    Seulement, comme n'importe quel outil, il y a "une bonne et une mauvaise manière" de l'utiliser: vous risqueriez gros à essayer de démarrer une tronçonneuse en tenant le porte chaine entre vos jambes. Et vous n'obtiendrez jamais un résultat satisfaisant en essayant d'utiliser un marteau pour enfoncer une vis quelque part.

    Dans le cas présent, non seulement vous voulez créer un nouvel outil, mais en plus, vous voulez définir de nouvelles règles de "bonne utilisation" de l'outil au travers de votre nouveau langage.

    Le problème est que plus vous mettrez de restrictions au niveau du langage, plus vous mettrez de restrictions au niveau des "règles de bonne utilisation de l'outil", plus vous limiterez l'outil dans ses possibilités.

    Et plus vous limiterez l'outil dans ses possibilités, plus vous augmenterez les chances de vous retrouver dans une situation dans laquelle "les règles de bonne utilisation de l'outil" vous empêcheront de faire ce que vous voulez avec l'outil.

    C'est la raison pour laquelle si je comprend l'idée qu'il faille imposer le fait de n'avoir qu'un propriétaire unique pour les ressources dynamiques, je vous conseille très fortement de faire au moins en sorte que ce propriétaire puisse "voyager" d'un scope à l'autre.

    Et, à titre personnel, je vous déconseille très fortement l'idée d'un "paramètre de sortie". Parce que cela implique de compter sur un "effet de bord" qui aura pour effet de rendre la moindre assertion quant à l'état interne de la donnée ayant servi comme telle beaucoup plus difficile, car dépendante des fonctions appelées.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  18. #18
    Membre confirmé
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Mai 2015
    Messages
    341
    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 : 341
    Par défaut Oui et non...
    Tout d'abord, merci de votre réponse, clair et argumentée. C'est un plaisir de dialoguer de la sorte.

    Citation Envoyé par koala01 Voir le message
    Justement, là, vous vous contredites...
    Prenons un exemple (code "C++ like" )
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    PtrType * allocate(/* params*/){
        /* some way to dynamically allocate memory */
       return popinter
    }
    void foo(){
        PtrType * ptr = allocate(/* param*/);
    }
    Qui est le propriétaire "légitime" du pointeur dans ce bout de code :question :

    Est-ce la fonciton allocate (qui a effectivement alloué la mémoire) ou est-ce la fonciton foo, qui récupère le pointeur sur la mémoire allouée

    Et, peu importe comment vous allez organiser les choses, même si c'est en introduisant un effet de bord volontaire (ce qui n'est que rarement une bonne solution) sous une forme qui pourrait être proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* here, allocate return true if allocation succeeds, false oftherwise */
    bool allocate(PtrType *& refToPointer, /* other params*/){
        /* some way to dynamically allocate memory  for refToPointer*/
       return success_or_failure;
    }
    
    void foo(){
        PtrType * ptr;
        allocate(ptr, /* other params */);
    }
    Au bout du compte, le propriétaire légitime d'une ressource allouée dynamiquement à partir d'une fonction appelée sera toujours ... la fonction appelante.
    Les allocations dynamiques ne seront pas faites via des fonctions, mais via une syntaxe. Le propriétaire de l'allocation sera donc le scope de la fonction qui utilisera cette syntax.

    Par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        int8 abloc[idx=10]            ; alloc static d'un tab. 1D de  10 int8 (.DATA).
        int8 d_bloc<idx=100>      ; alloc dyn tab. 1D de int8, désallouable, de taille fixe 100.
    Citation Envoyé par koala01 Voir le message
    Alors, oui, vous avez (en partie seulement !!!!) raison : il ne peut y avoir qu'un seul propriétaire.

    Cependant, vous oubliez une précision des plus importantes : il ne peut y avoir qu'un seul propriétaire A UN INSTANT DONNE DE L'EXECUTION. Et ce sont ces six mots qui font toute la différence.

    Car, sans ajouter cette précision, ce que vous dites, c'est que c'est la foncton allocate qui devrait être le propriétaire légitime de la ressource allouée dynamiquement, et donc, qu'elle devrait prendre en charge la libération de cette ressource lorsque l'exécution en quitte le scope. C'est à dire ... juste avant de rendre la main à la fonction foo (dans mon exemple). Ce qui rendrait l'allocation dynamique de la ressource ... totalement inutile, vu qu'elle ne pourrait pas être utilisée hors de la fonction qui l'a effectuée (ou en dehors des fonctions que la fonction allocate pourrait appeler).
    Comme dit plus haut, les allocations dynamiques ne se feront pas via des fonctions du genre alloc(), le problème que vous soulevez ne pourra donc jamais se produire me semble-t-il.

    Citation Envoyé par koala01 Voir le message
    Par contre, si vous précisez qu'il ne peut y avoir qu'un seul propriétaire légitime de la ressource A UN INSTANT DONNE DE L'EXECUTION, alors, là, les choses deviennent différentes.

    Parce que cela signifie que la fonction allocate peut "refiler la patate chaude" à la fonction foo (dans mon exemple toujours) pour que ce soit la sortie du scope de cette fonction qui occasionne la libération correcte de la ressource; et, pourquoi pas, que la fonction foo déciide de refiler cette responsabilité soit à une des fonctions qu'elle appelle par la suite, soit à la fonction qui l'a appelée.
    Oui, il n'y a qu'un seul propriétaire, toujours, pas juste "A un instant donné".

    Citation Envoyé par koala01 Voir le message
    Justement, comme je viens de vous l'expliquer, c'est sur ce point que vous vous trompez.

    Sans possibilité de transférer la propriété, vous allez vous retrouver à devoir allouer l'ensemble des ressources ... dans votre fonction principale. Car ce sera le seul endroit où vous pourrez être sur que l'ensemble des ressources restera accessible aussi longtemps que vous pourriez en avoir besoin; ce qui risque d'amener un autre problème du "et si j'ai besoin de plus d'espace".
    Excusez-moi, je n'avais pas précisé que les allocations ne sont pas faites via des fonctions, mais via une syntaxe du langage, et des allocations pourront se faire dans n'importe quelle fonction, mais c'est cette fonction qui en sera l'unique et éternel propriétaire.

    Citation Envoyé par koala01 Voir le message
    Non, vous pouvez imposer le fait qu'il n'y a jamais plus d'un propriétaire A LA FOIS pour une ressource. Ce sera d'ailleurs sans doute une excellente chose.
    Ce sera le cas, mais le propriétaire restera toujours le scope de la fonction qui a fait l'allocation (qui elle se fait sans utiliser de fonctions).

    Citation Envoyé par koala01 Voir le message
    Par contre, si vous empêchez le transfert de responsabilité d'un propriétaire vers un autre (étant entendu que "l'ancien" propriétaire n'a alors plus rien à dire quant à la libération de la ressource), vous allez vous limiter dans vos possibilités de manière horrible...
    Il n'a y pas de transfert de propriété, mais un "prêt" uniquement. Un "prêt" qui via la syntaxe, est obligatoirement "remboursé" (rendu).

    Citation Envoyé par koala01 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Il ne faut pas confondre la notion "d'utilisateur" et celle de "propriétaire". Un pointeur donné comme paramètre de sortie, peut être utilisé par d'autres fonctions, mais ne peut leur en donner la "propriété", car elle-même n'en a pas la propriété..
    Justement, je suis le premier à insister sur la nécessité de clairement séparer la propriétaire de l'utilisateur.
    Je suis parfaitement d'accord avec vous sur ce point.

    Citation Envoyé par koala01 Voir le message
    Ce dont vous ne semblez pas avoir conscience en terme de ressource allouée dynamiquement, c'est que soit vous devez transférer aux fonctions appelées la ressource elle même sous une forme qui empêchera la fonction appelée de libérer la ressource en question (en C++, nous utiliserons de préférence une référence pour cela), soit c'est carrément le propriétaire en entier que vous devrez transférer, pour que, si vous décidez -- à un moment donné, dans la fonction appelée -- que la ressource peut effectivement être libérée, ce soit effectivement fait en demandant au propriétaire de s'en charger.

    Par exemple, en C++, cela donnerait quelque chose comme
    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
    28
    29
    30
    31
    32
    33
    34
    35
    /* Cette fonction utilise une ressource, mais ne peut décider de la libérer */
    void user_no_release( Type /*const */ &  ref){
        /* on peut utiliser la ressource, la modifier si la référence
         * n'est pas constante, mais pas décider de libérer la ressource
         */
    }
    /* cette fonction peut décider de libérer la ressource */
    void possibly_release(std::unique_ptr<Type> & ptr){
        /* some stuff here*/
        if( some_condition)
            ptr.release(); /* parce qu'on a le propriétaire en entier,
                            * on peut lui demander de libérer la ressource
                            */
    }
    void foo(){
         /* le propriétaire de la ressource n'est pas la fonction foo
          * mais bien la donnée unique_pointer
          */
         std::unique_ptr<Type> unique_pointer= std::make_unique<Type>(/* param*/);
         /* on peut appeler une fonction utilisatrice en transférant "ce qui est pointé
          * par le pointeur sous jacent
          */
        user_no_release(*unique_pointer);
        /* on peut aussi transmettre le propriétaire en entier */
       possibly_release(unique_pointer);
       /* seulement, arrivé ici, unique_pointer pointe peut être
        * (si "some_condition" a été vérifiée dans la fonciton appelée)
        * vers une adresse connue pour être invalide (nullptr)
        * Si bien que, si on veut encore accéder au pointeur sous-jacent,
        * il faut commencer par s'assurer de la validité du pointeur
        */
        if(unique_pointer) // si le pointeur est valide
            user_no_release(*unique_pointer) // on peut continuer à l'utiliser
    } // dans tous les cas, comme on quite le scope dans lequel unique_pointer a été déclaré
      // si le pointeur sous-jacent est encore valide, la ressource pointée sera libérée ici
    Par simplicité code tend à valider le fait qu'il n'y a qu'un seul et unique propriétaire, ce qui est tout à fait vrai au demeurant.
    On peut donner cette ressource à une autre fonction sans soucis, elle en sera l'utilisatrice **unique**, et pourra par exemple ajouter des éléments à un tableau, mais sans en devenir le propriétaire. Ce qui sera ajouté au tableau ne sera évidemment pas désalloué en quittant la fonction qui a fait ces ajouts dans le tableau qu'elle utilise. Le tableau sera étendu, les "ajout" étant fait dans le tableau seront sous la responsabilité du propriétaire unique du tableau. Un "ajout" n'est pas un "transfert", car "l'ajout" sera fait dans le tableau, son propriétaire les désallouera lorsque son scope se terminera. Ces "ajouts" seront également fait via la syntax, sans appel de fonctions.

    Citation Envoyé par koala01 Voir le message
    Mais, qu'est ce qui pourrait empêcher ce propriétaire de "se balader" d'une fonction à une autre, de manière à ce que la sortie de scope d'une autre fonction que celle dans laquelle il aura été déclaré provoque effectivement et automatiquement la libération de la ressources

    Après tout, si la fonction foo prenait une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void foo(std::vector<std::unique_ptr<Type> & tab){
        tab.emplace_back(std::make_unique<Type>(/*params*/));
        size_t index= tab.size()-1;
        /* on peut appeler nos deux fonctions */
        user_no_release(*(tab[index]));
        possibly_release(tab[index]);
    }
    la sortie se scope provoquant la libération de la ressource se ferait ... au niveau de la fonction dans laquelle le tableau a été déclaré.

    Mais qui est le propriétaire légitime de la ressource
    Je comprend bien. Et je dirais que c'est une raison de plus pour prendre le temps de réfléchir correctement à l'outil que vous essayez de créer.
    Le propriétaire d'un tableau sera le propriétaire des éléments qu'on lui ajoute car ces ajouts seront fait via le tableau lui-même, qui a été prêté.

    Citation Envoyé par koala01 Voir le message
    Car, finalement, un langage de programmation n'est jamais qu'un langage comme le Français : un ensemble de conventions permettant à deux intervenants de se comprendre. La seule différence avec les langages "natifs" étant que l'un des intervenant est ... aussi bête qu'un ordinateur. C'est d'ailleurs la raison pour laquelle l'ordinateur devra utiliser un outil (interpréteur ou compilateur) pour arriver à comprendre ce que les humains auront écrit.
    Je suis parfaitement d'accord avec vous.

    Citation Envoyé par koala01 Voir le message
    Seulement, comme n'importe quel outil, il y a "une bonne et une mauvaise manière" de l'utiliser: vous risqueriez gros à essayer de démarrer une tronçonneuse en tenant le porte chaine entre vos jambes. Et vous n'obtiendrez jamais un résultat satisfaisant en essayant d'utiliser un marteau pour enfoncer une vis quelque part.
    Heureusement, je ne suis pas un manuel ;-)

    Citation Envoyé par koala01 Voir le message
    Dans le cas présent, non seulement vous voulez créer un nouvel outil, mais en plus, vous voulez définir de nouvelles règles de "bonne utilisation" de l'outil au travers de votre nouveau langage.

    Le problème est que plus vous mettrez de restrictions au niveau du langage, plus vous mettrez de restrictions au niveau des "règles de bonne utilisation de l'outil", plus vous limiterez l'outil dans ses possibilités.

    Et plus vous limiterez l'outil dans ses possibilités, plus vous augmenterez les chances de vous retrouver dans une situation dans laquelle "les règles de bonne utilisation de l'outil" vous empêcheront de faire ce que vous voulez avec l'outil.
    Je comprend parfaitement votre point de vue, mais l'idée n'est vraiment pas de "mettre des verrous partout", au contraire. Mais oui, j'essaye que ce langage soit "carré", sans ambiguïté, facile d'utilisation, et au code source très lisible.

    Sauf erreur de ma part, je ne vois pas où j'impose des "limites", mais oui, j'essaye de "penser" autrement. Je ne prétend pas définir de nouvelles règles de "bonne utilisation". J'essaye que mon langage puisse intégrer de "bonnes pratiques", limitant les erreurs qu'un langage comme le C permet de faire.

    Le tout avec une syntax simple, légère, et facilement compréhensible. Je n'arriverais peut-être pas à mon objectif, je vais peut-être tomber sur des cas "non gérables", mais jusqu'à présent, je n'en ai pas encore rencontré. Si je rencontre ces soucis, et bien je penserais à d'autres moyens. Je n'ai pas la prétention de me mettre en concurrence avec d'autres développeurs de compilateur, ou de remplacer des "théoriciens" bien plus compétant que moi. Je reste très humble concernant ce que je tente de faire.

    Citation Envoyé par koala01 Voir le message
    C'est la raison pour laquelle si je comprend l'idée qu'il faille imposer le fait de n'avoir qu'un propriétaire unique pour les ressources dynamiques, je vous conseille très fortement de faire au moins en sorte que ce propriétaire puisse "voyager" d'un scope à l'autre.
    Le "propriétaire" d'une ressource peut la laisser voyager, la laisser être utilisée (mais pas la désallouer) dans d'autres scopes, tout en restant le propriétaire. Si vous donnez les clefs de votre voiture à un ami pour quelque raison que ce soit, il est utilisateur de votre voiture, mais vous en rester le propriétaire, et il devra vous rendre les clefs.

    Le fait que le scope de la fonction reste propriétaire de ce qu'elle a alloué, a pour but premier d'empêcher toute une série de bugs, sans employer des méthodes comme celles utilisées par Rust ou Ada (par exemple, et j'ajoute que je n'ai rien contre ces 2 langages). Le concept d'ownership a des limites, d'ou l'utilisation des références non mutables et des shared références mutable, qui nécessitent l'utilisation d'un "Borrow Checker" pour surveiller tout cela. J'essaye (sans prétendre que j'y arriverais) d'obtenir le même résultat de code "safe & secure", mais plus simplement, avec une autre approche.

    En fait, la notion même de "propriétaire" n'a pas besoin d'être évoquée pour utiliser le langage. Je n'ai d'ailleurs entendu parler de cette notion de "propriétaire" que via Ada & Rust. C'est une notion qui, me semble-t-il, a été développée pour empêcher des **bugs** souvent rencontrés si la gestion de la mémoire dynamique est faite "à la main" (comme en C via alloc(), malloc(), ...).

    Citation Envoyé par koala01 Voir le message
    Et, à titre personnel, je vous déconseille très fortement l'idée d'un "paramètre de sortie". Parce que cela implique de compter sur un "effet de bord" qui aura pour effet de rendre la moindre assertion quant à l'état interne de la donnée ayant servi comme telle beaucoup plus difficile, car dépendante des fonctions appelées.
    Là, j'avoue que je ne comprend pas bien ce que vous voulez dire par "effet de bord". Un "paramètre de sortie" est géré comme un autre argument d'une fonction. Il permet simplement de faire une différence "sémantique" entre 2 types des paramètres, ceux d'entrées qui sont entre () et 'constant' par défaut, et les valeurs que doit retourner une fonction, qui sont 'variables' par défaut. Au niveau du compilateur, ils sont passé via la stack comme les autres arguments, tout comme en POO un argument est passé comme référence à un objet particulier. En python, cela est "explicite", via le 'self' qui est le premier argument de chaque méthode. En C++, c'est le 'this', qui est lui implicite.

    Le fait que ce soit les variables qui sont assignés soit automatiquement les "paramètres de sortie" ne pose pas de limite. Si on passait ces arguments de manière classique, via deux autres arguments, un appel de cette fonction devrait comporter ces arguement lors de l'appel, sinon le compilateur émettra une erreur. Dans mon langage, si une fonction retourne "plusieurs valeurs", il faut qu'il y ai des variables, du type correct, pour "récupérer" ces valeurs.

    Je vous remercie encore du temps que vous passez à "mettre à l'épreuve" les idées que je tente d'implémenter dans mon langage. J'espère avoir répondu à vos questions, mais si ce n'est pas le cas, ou si ces réponses vous semble "fausses/mauvaises/érronées", je serais très heureux de poursuivre cette discution. De même, si je n'ai pas moi-même compris une de vos réponses, veuillez m'en excuser, et n'hésitez pas si vous le désirez, a reprendre et préciser ou expliquer autrement ce que je n'aurais pas bien compris.

    Je ne demande qu'a apprendre ;-)

    Encore merci.
    BàV et Peace & Love.

  19. #19
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Comme dit plus haut, les allocations dynamiques ne se feront pas via des fonctions du genre alloc(), le problème que vous soulevez ne pourra donc jamais se produire me semble-t-il.
    Ce que vous ne semlez pas comprendre, c'est, que l'allocation dynamique soit effectuée au travers d'une fonciton comme malloc, d'un opérateur comme new ou de n'importe quel autre moyen syntaxique mis à votre disposition, elle s'effectuera forcément à l'intérieur d'une fonction, autrement, vous vous obligez à effectuer l'allocation sur des variables globales.

    Donc, par définition, si vous dites que la fonciton dans laquelle l'allocation dynamique représente le scope de validité de cette allocation, la ressource devra être libérrée, au plus tard, au moment où vous quitterez le scope de cette fonction.
    Oui, il n'y a qu'un seul propriétaire, toujours, pas juste "A un instant donné".
    Justement, non.

    On peut ne pas accepter d'avoir deux propriétaires de la même ressource en même temps -- même si, sous certains aspects, cela peut rendre les choses bien plus compliquées -- mais cela n'implique pas forcément que le propriétaire de la ressource ne puisse absolument pas changer au fil du temps.

    C'est un peu comme si vous achetiez une voiture. Vous en êtes le propriétaire légitime pendant plusieurs années, puis, pour une raison quelconque, vous décidez d'en changer alors qu'elle fonctionne parfaitement. Vous décidez donc de la revendre à quelqu'un d'autre (en occasion), afin de pouvoir acheter la nouvelle, dont vous deviendrez le propriétaire.

    Si vous ne vous donnez pas la possibilité d'effectuer un tel "transfert de propriété", tout votre design, toute votre organisation des données doit être envisagée de manière totalement différente, avec des difficultés bien plus importantes de mise en oeuvre.

    Je ne dit pas qu'il est impossible de concevoir vos projets de la sorte, je dis juste que cette manière de concevoir vos projets apporte beaucoup plus de complexité qui aurait pu (aurait du pouvoir) être évitée.

    Pour citer Einstein lui-même, il faut
    Rendre les choses aussi complexes que nécessaires, mais guère plus
    Or, en empêchant le transfert de propriété, vous rendez les choses ... beaucoup plus complexes que nécessaire. Et, finalement, pour un bénéfice beaucoup trop limitée par rapport à la complexité ajoutée.

    Excusez-moi, je n'avais pas précisé que les allocations ne sont pas faites via des fonctions, mais via une syntaxe du langage, et des allocations pourront se faire dans n'importe quelle fonction, mais c'est cette fonction qui en sera l'unique et éternel propriétaire.
    Là non plus, cela ne marche pas.

    Car cela signifie que la seule possibilité d'accéder à la ressource allouée dynamiquement (à l'exception de la fonction dans laquelle l'allocation s'effectue) passe par l'appel, depuis la fonction ayant alloué la ressource, de fonctions tierses.

    Mais que se passe-t-il alors si la fonction qui a appelé la fonciton s'occupant d'allouer la ressource souhaite profiter de cette ressource Peu importe que la fonction appelante récupère cette ressource au travers d'une valeur de retour ou d'un "paramètre de sortie", si la ressource est -- effectivement -- libérée au moment où l'exécution quitte le scope de la fonction "allocative", la ressource n'est ... tout simplement plus disponible pour la fonction appelante.

    Il n'a y pas de transfert de propriété, mais un "prêt" uniquement. Un "prêt" qui via la syntaxe, est obligatoirement "remboursé" (rendu).
    Le problème, c'est que vous vous limitez alors à une logique du genre (code C++ style) de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void foo(){
        Type res = allocated_ressource;
        fonction_pret_1(res);
        fonction_tret_2(res);
        /* il pourrait y avoir des tests, des boucles et tout ce que l'on veut */
    }  // res est libéré car sortie de scope
    Et j'en reviens à ma question: que se passe-t-il si la fonction qui appelle foo s'attend à récupérer la ressource Si vous ne pouvez pas garantir que la fonction appelante sera en mesure de récupérér une ressource valide et, par définitive, d'en devenir le propriétaire légitime, tout votre design devra être adapté en conséquence... Avec une complexité bien supérieure à ce que vous permettrait le fait d'accepter le transfert de propriété.

    Ce dont vous ne semblez pas avoir conscience en terme de ressource allouée dynamiquement, c'est que soit vous devez transférer aux fonctions appelées la ressource elle même sous une forme qui empêchera la fonction appelée de libérer la ressource en question (en C++, nous utiliserons de préférence une référence pour cela), soit c'est carrément le propriétaire en entier que vous devrez transférer, pour que, si vous décidez -- à un moment donné, dans la fonction appelée -- que la ressource peut effectivement être libérée, ce soit effectivement fait en demandant au propriétaire de s'en charger.

    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
    28
    29
    30
    31
    32
    33
    34
    35
    /* Cette fonction utilise une ressource, mais ne peut décider de la libérer */
    void user_no_release( Type /*const */ &  ref){
        /* on peut utiliser la ressource, la modifier si la référence
         * n'est pas constante, mais pas décider de libérer la ressource
         */
    }
    /* cette fonction peut décider de libérer la ressource */
    void possibly_release(std::unique_ptr<Type> & ptr){
        /* some stuff here*/
        if( some_condition)
            ptr.release(); /* parce qu'on a le propriétaire en entier,
                            * on peut lui demander de libérer la ressource
                            */
    }
    void foo(){
         /* le propriétaire de la ressource n'est pas la fonction foo
          * mais bien la donnée unique_pointer
          */
         std::unique_ptr<Type> unique_pointer= std::make_unique<Type>(/* param*/);
         /* on peut appeler une fonction utilisatrice en transférant "ce qui est pointé
          * par le pointeur sous jacent
          */
        user_no_release(*unique_pointer);
        /* on peut aussi transmettre le propriétaire en entier */
       possibly_release(unique_pointer);
       /* seulement, arrivé ici, unique_pointer pointe peut être
        * (si "some_condition" a été vérifiée dans la fonciton appelée)
        * vers une adresse connue pour être invalide (nullptr)
        * Si bien que, si on veut encore accéder au pointeur sous-jacent,
        * il faut commencer par s'assurer de la validité du pointeur
        */
        if(unique_pointer) // si le pointeur est valide
            user_no_release(*unique_pointer) // on peut continuer à l'utiliser
    } // dans tous les cas, comme on quite le scope dans lequel unique_pointer a été déclaré
      // si le pointeur sous-jacent est encore valide, la ressource pointée sera libérée ici
    Par simplicité code tend à valider le fait qu'il n'y a qu'un seul et unique propriétaire, ce qui est tout à fait vrai au demeurant.
    On peut donner cette ressource à une autre fonction sans soucis, elle en sera l'utilisatrice **unique**, et pourra par exemple ajouter des éléments à un tableau, mais sans en devenir le propriétaire.
    uniquement pour les fonction qui seraient appelée -- de manière directe ou indirecte -- par foo.

    La question reste malgré tout la même: qu'en est il de la fonction qui fera appel à foo, en particulier si elle souhaite récupérer la ressource allouée par foo

    Dites moi que vous ne voulez pas que la fonction qui appelle foo puisse récupérer la ressource, que vous comprenez les défis auxquels cette décision vous fera faire face, je vous foutrai la paix, et je vous laisserai vous dépêtrer avec vos problèmes (quitte à essayer de vous aider à vous en sortir).

    Ce qui sera ajouté au tableau ne sera évidemment pas désalloué en quittant la fonction qui a fait ces ajouts dans le tableau qu'elle utilise.
    C'est justement là le problème :pour que la ressource allouée dans votre fonction et ajoutée à votre tableau ne soit libérée que lorsque le tableau est détruit, il faut ... transférer la propriété de la ressource au tableau.

    Autrement, c'est la fonction qui s'occupe de l'allocation de la ressource qui, en tant que propriétaire, doit s'assurer de la libérer correctement.

    Cette phrase à elle seule contredit tout ce que vous dite concernant l'impossibilité de transférer la propriété et valide tout ce que moi je peux dire concernant l'absolue nécessité d'autoriser le transfert de propriété.

    Le tableau sera étendu, les "ajout" étant fait dans le tableau seront sous la responsabilité du propriétaire unique du tableau.
    Même pas... la responsabilité de libération des éléments du tableau doit échoire ... au tableau lui-même, pas au propriétaire du tableau.

    Car, autrement, cela voudrait dire que le seul moyen de supprimer un élément et de s'assurer que la ressource sera correctement libérée serait de le faire ... dans la fonction dans laquelle le tableau existe.

    Un "ajout" n'est pas un "transfert", car "l'ajout" sera fait dans le tableau, son propriétaire les désallouera lorsque son scope se terminera. Ces "ajouts" seront également fait via la syntax, sans appel de fonctions.
    Justement, non...
    L'ajout d'un élément au tableau est un transfert de propriété, de la fonction qui crée l'élément vers le tableau qui le contient.
    Je comprend parfaitement votre point de vue, mais l'idée n'est vraiment pas de "mettre des verrous partout", au contraire.
    Pourtant, en décidant qu'une fonciton ne peut pas transmettre la propriété de la ressource qu'elle alloue à la fonction qui l'a appelée, vous placez un énorme verrou qu'il vous sera difficile de contourner
    Mais oui, j'essaye que ce langage soit "carré", sans ambiguïté, facile d'utilisation, et au code source très lisible.
    Et c'est tout à votre honneur!

    Le point est que, en l'état, votre réflexion me semble "manquer de profondeur", sans doute à cause d'un manque d'expérience, car vous n'envisagez pas un cas qui se présentera bien plus souvent que vous ne semblez le croire.
    Je ne prétend pas définir de nouvelles règles de "bonne utilisation".
    C'est pourtant ce que vous faites en essayant de définir votre propre langage.

    C'est ce que fait n'importe qui en essayant de définir un langage : définir un ensemble de règles permettant à un outil de comprendre ce que l'on attend de lui
    J'essaye que mon langage puisse intégrer de "bonnes pratiques", limitant les erreurs qu'un langage comme le C permet de faire.
    Et c'est une excellente chose.

    Cela ne doit juste pas se faire en mettant au placard des pratiques au sujet desquelles cinquante ans d'évolution dans les langages ont clairement démontré qu'elles étaient incontournables.


    Le "propriétaire" d'une ressource peut la laisser voyager, la laisser être utilisée (mais pas la désallouer) dans d'autres scopes, tout en restant le propriétaire. Si vous donnez les clefs de votre voiture à un ami pour quelque raison que ce soit, il est utilisateur de votre voiture, mais vous en rester le propriétaire, et il devra vous rendre les clefs.
    C'est vrai lorsque vous êtes au niveau d'une fonction appelante...

    Mais une fonction ne vaut que si elle est appelée. La question est donc toujours la même : que se passe-t-il si la fonction est appelée par une fonction qui souhaite récupérer la ressource allouée
    Le fait que le scope de la fonction reste propriétaire de ce qu'elle a alloué, a pour but premier d'empêcher toute une série de bugs,
    L'initiative est louable, certes, mais elle pose une série de problème qui seront bien plus difficile à prendre en compte et à résoudre que le simple fait d'accepter l'idée que la propriété d'une ressource allouée par une fonction puisse être prise en charge par (et donc transférée à) la fonction ayant appelé la fonction responsable de l'allocation de la ressource.
    Là, j'avoue que je ne comprend pas bien ce que vous voulez dire par "effet de bord".
    On parle d'effet de bord lorsqu'une donnée issue de la fonction appelante est modifiée par la fonction appelée.
    Un "paramètre de sortie" est géré comme un autre argument d'une fonction.
    non, parce que le paramètre est destiné à être modifié par la fonction appelée, et les modifications apportées à ce paramètre seront répercutées sur la donnée fournie par la fonction appelante.

    Là, j'avoue que je ne comprend pas bien ce que vous voulez dire par "effet de bord". Un "paramètre de sortie" est géré comme un autre argument d'une fonction. Il permet simplement de faire une différence "sémantique" entre 2 types des paramètres, ceux d'entrées qui sont entre () et 'constant' par défaut, et les valeurs que doit retourner une fonction, qui sont 'variables' par défaut.
    Le fait est que, si une fonction a besoin d'une donnée externe (comprenez : dont elle ne peut définir par elle même la valeur à l'exécution), il vaut mieux que cette donnée soit immuable ("paramètre d'entrée" pour reprendre vos propres termes), mais que si elle doit rendre une donnée accessible à la fonction qui l'a appelée, il vaut mieux le faire en lui permettant de renvoyer cette donnée plutôt qu'en décidant de modifier une donnée fournie par la fonction appelante (vos "paramètres de sortie"), justement, à cause de l'effet de bord que le fait de modifier cette donnée fournie par la fonction appelante implique.
    tout comme en POO un argument est passé comme référence à un objet particulier. En python, cela est "explicite", via le 'self' qui est le premier argument de chaque méthode. En C++, c'est le 'this', qui est lui implicite.
    Ah, OK... Je vois ce qui vous intrigue...

    Le fait est que this ou self ne sont pas "vraiment" des paramètres de sortie. Enfin, si, techniquement parlant, ce sont bel et bien des paramètres de sorite. Seulement, on entre là dans le domaine des fonctions membres. Des fonctions qui n'existent que parce qu'il existe un type de donnée à partir duquel les appeler, et qui ne peuvent être appelées qu'au départ d'une instance du type de donnée en question.

    Lorsque la fonction membre modifier les données internes de l'objet à partir duquel elle est appelée, il y a bien "effet de bord", cependant, il reste malgré tout limité, et s'il y avait moyen de l'éviter (entre autres, pour les types de données ayant sémantique de valeur), ce serait pas plus mal.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  20. #20
    Membre confirmé
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Mai 2021
    Messages
    103
    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 : 103
    Par défaut
    Citation Envoyé par OuftiBoy Voir le message
    Il n'a y pas de transfert de propriété, mais un "prêt" uniquement. Un "prêt" qui via la syntaxe, est obligatoirement "remboursé" (rendu).
    Si vous creusez l'idée, cela vous amène doucement à une sémantique de prêt à la Rust.
    Là vous vous en passez parce que vos exemples sont essentiellement mono-thread et non-concurrent. -> Je prête ma propriété et j’attends le retour de fonction pour récupérer ma propriété.
    Avec votre sémantique, je ne suis même pas sûr que vous puissiez prêter ce que vous avez emprunté.

    Mais considérons maintenant des exemples avec appels concurrents ou multithreads, alors le processus appelant peut continuer de s'exécuter: quelles sont les règles d'utilisation des données déjà prêtées?
    Comment faites vous pour prêter à plusieurs processus? (ce qui peut être utile pour partager des données entres plusieurs threads)

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